diff --git a/.editorconfig b/.editorconfig index b0d0662bf8..0e4883082c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -339,6 +339,7 @@ csharp_space_between_square_brackets = false # warn when using var for built-in types, # warn when using var when the type is not apparent, and # warn when not using var when the type is apparent +# warn when using simplified "using" declaration ############################################################################### [*.cs] csharp_prefer_braces = true:silent @@ -367,6 +368,6 @@ csharp_style_throw_expression = true:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_var_for_built_in_types = false:warning -csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = never csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = false:warning diff --git a/.gitattributes b/.gitattributes index b9a9ddd4c3..24a21b60da 100644 --- a/.gitattributes +++ b/.gitattributes @@ -62,14 +62,30 @@ # normalize to Windows-style line endings and *.sln text eol=crlf merge=union # treat as binary +*.basis binary *.bmp binary +*.dds binary *.dll binary +*.eot binary *.exe binary *.gif binary *.jpg binary +*.ktx binary +*.otf binary +*.pbm binary +*.pdf binary *.png binary -*.ttf binary +*.ppt binary +*.pptx binary +*.pvr binary *.snk binary +*.tga binary +*.ttc binary +*.ttf binary +*.woff binary +*.woff2 binary +*.xls binary +*.xlsx binary # diff as plain text *.doc diff=astextplain *.docx diff=astextplain @@ -77,6 +93,7 @@ *.pdf diff=astextplain *.pptx diff=astextplain *.rtf diff=astextplain +*.svg diff=astextplain *.jpg filter=lfs diff=lfs merge=lfs -text *.jpeg filter=lfs diff=lfs merge=lfs -text *.bmp filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 01c09d2231..d45d98b393 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,6 +29,10 @@ * Ask any question about how to use ImageSharp in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General). +#### Code of Conduct +This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + And please remember. ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible imageprocessing available to all. Open Source can only exist with your help. Thanks for reading! diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000000..d76d68c1a5 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,148 @@ +name: Build + +on: + push: + branches: + - master + tags: + - "v*" + pull_request: + branches: + - master + +jobs: + Build: + strategy: + matrix: + options: + - os: ubuntu-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: true + - os: windows-latest + framework: netcoreapp2.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x86 + codecov: false + + runs-on: ${{matrix.options.os}} + + steps: + - uses: actions/checkout@v2 + + - name: Install NuGet + uses: NuGet/setup-nuget@v1 + + - name: Setup Git + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + git fetch --prune --unshallow + git submodule -q update --init --recursive + + - name: Fetch Tags for GitVersion + run: | + git fetch --tags + + - name: Fetch master for GitVersion + if: github.ref != 'refs/heads/master' + run: git branch --create-reflog master origin/master + + - name: Install GitVersion + uses: gittools/actions/setup-gitversion@v0.3 + with: + versionSpec: "5.1.x" + + - name: Use GitVersion + id: gitversion # step id used as reference for output values + uses: gittools/actions/execute-gitversion@v0.3 + + - name: Setup DotNet SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.101" + + - name: Build + shell: pwsh + run: ./ci-build.ps1 "${{steps.gitversion.outputs.nuGetVersion}}" "${{matrix.options.framework}}" + + - name: Test + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + CI: True + XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit + + # Avoid "Please provide the repository token to upload reports via `-t :repository-token`" + # https://community.codecov.io/t/whitelist-github-action-servers-to-upload-without-a-token/491/10 + # https://github.community/t5/GitHub-Actions/Make-secrets-available-to-builds-of-forks/m-p/42814/highlight/true#M5129 + - name: Update Codecov + uses: iansu/codecov-action-node@v1.0.0 + if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') + with: + token: 0ef021c7-2679-4012-b42f-4bed33d99450 + flags: unittests + + Publish: + needs: [Build] + + runs-on: windows-latest + + if: (github.event_name == 'push') + + steps: + - uses: actions/checkout@v2 + + - name: Install NuGet + uses: NuGet/setup-nuget@v1 + + - name: Setup Git + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + git fetch --prune --unshallow + git submodule -q update --init --recursive + + - name: Fetch Tags for GitVersion + run: | + git fetch --tags + + - name: Fetch master for GitVersion + if: github.ref != 'refs/heads/master' + run: git branch --create-reflog master origin/master + + - name: Install GitVersion + uses: gittools/actions/setup-gitversion@v0.3 + with: + versionSpec: "5.1.x" + + - name: Use GitVersion + id: gitversion # step id used as reference for output values + uses: gittools/actions/execute-gitversion@v0.3 + + - name: Setup DotNet SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.101" + + - name: Pack + shell: pwsh + run: ./ci-pack.ps1 "${{steps.gitversion.outputs.nuGetVersion}}" + + - name: Publish to MyGet + shell: pwsh + run: nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v2/package + # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org diff --git a/.gitignore b/.gitignore index d8f376a419..a89cfcf104 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -216,7 +216,7 @@ artifacts/ *.csproj.bak #CodeCoverage -/ImageSharp.Coverage.xml +*.lcov # Tests **/Images/ActualOutput diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6fd38484dd..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,43 +0,0 @@ -language: csharp -solution: ImageSharp.sln - -matrix: - include: - - os: linux # Ubuntu 16.04 - dist: xenial - sudo: required - dotnet: 2.1.603 - mono: latest -# - os: osx # OSX 10.11 -# osx_image: xcode7.3.1 -# dotnet: 1.0.0-preview2-003121 -# mono: latest - -branches: - only: - - master - - coverity_scan - -script: - - git submodule -q update --init - - dotnet restore - - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp2.1" - -env: - global: - # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created - # via the "travis encrypt" command using the project repo's public key - - secure: "rjMvEMN9rpvIXqXqCAAKzbHyABzr7E4wPU/dYJ/mHBqlCccFpQrEXVVM1MfRFXYuWZSaIioknhLATZjT5xvIYpTNM6D57z4OTmqeRHhYm80=" - -before_install: - - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- - -addons: - coverity_scan: - project: - name: "SixLabors/ImageSharp" - description: "Build submitted via Travis CI" - notification_email: james_south@hotmail.com - build_command_prepend: "dotnet restore" - build_command: "dotnet build -c Release" - branch_pattern: coverity_scan \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index c772e647ce..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceRoot}/tests/ImageSharp.Benchmarks/bin/Debug/netcoreapp2.0/ImageSharp.Benchmarks.dll", - "args": [], - "cwd": "${workspaceRoot}/samples/AvatarWithRoundedCorner", - // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window - "console": "internalConsole", - "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart" - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 82aaa2f8d0..0000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "0.1.0", - "command": "dotnet", - "isShellCommand": true, - "args": [], - "tasks": [ - { - "taskName": "build", - "args": [ "ImageSharp.sln" ], - "isBuildCommand": true, - "showOutput": "always", - "problemMatcher": "$msCompile" - }, - { - "taskName": "build benchmark", - "suppressTaskName": true, - "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp2.0", "-c", "Release" ], - "showOutput": "always", - "problemMatcher": "$msCompile" - }, - { - "taskName": "test", - "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp2.0"], - "isTestCommand": true, - "showOutput": "always", - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..b34bbb41a3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct +This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). \ No newline at end of file diff --git a/CodeCoverage.runsettings b/CodeCoverage.runsettings deleted file mode 100644 index d9c0848f13..0000000000 --- a/CodeCoverage.runsettings +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - .*ImageSharp.dll - - - .*tests* - .*Tests* - - - - - - - - \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index efe4cc9665..388f873574 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ $(MSBuildThisFileDirectory)artifacts/ $(ImageSharpProjectCategory)/$(MSBuildProjectName) - https://github.com/SixLabors/ImageSharp/ + https://github.com/SixLabors/ImageSharp/ @@ -29,13 +29,38 @@ true - - true + + + + $(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE;SUPPORTS_HOTPATH + + + $(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_CODECOVERAGE + + + $(DefineConstants);SUPPORTS_MATHF;SUPPORTS_CODECOVERAGE + + + $(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_CODECOVERAGE + + + $(DefineConstants);SUPPORTS_CODECOVERAGE - - - - false + + $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_CODECOVERAGE @@ -56,26 +81,33 @@ Copyright © Six Labors and Contributors strict;IOperation true - 7.3 + 8.0 en true - https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png + sixlabors.imagesharp.128.png Apache-2.0 $(RepositoryUrl) true git https://www.myget.org/F/sixlabors/api/v3/index.json; - https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json; https://api.nuget.org/v3/index.json; + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json; - 002400000c8000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97 + 00240000048000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97 true + true - + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets index 1551a29d8c..0f02d7e320 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -15,33 +15,23 @@ $(DefineConstants);$(OS) - - - - - - - - - - - - - - - - - + + + + + + + - + + + - - - + diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000000..f2a251c55a --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,7 @@ +continuous-delivery-fallback-tag: ci +branches: + master: + tag: unstable + mode: ContinuousDeployment + pull-request: + tag: pr diff --git a/ImageSharp.sln b/ImageSharp.sln index d4a0419eed..f1d4afef41 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -3,24 +3,20 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28902.138 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore .gitmodules = .gitmodules - .travis.yml = .travis.yml - appveyor.yml = appveyor.yml - build.cmd = build.cmd - build.ps1 = build.ps1 - codecov.yml = codecov.yml - CodeCoverage.runsettings = CodeCoverage.runsettings + ci-build.ps1 = ci-build.ps1 + ci-pack.ps1 = ci-pack.ps1 + ci-test.ps1 = ci-test.ps1 Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets - ImageSharp.sln.DotSettings = ImageSharp.sln.DotSettings + GitVersion.yml = GitVersion.yml LICENSE = LICENSE README.md = README.md - run-tests.ps1 = run-tests.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}" @@ -36,25 +32,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEM .github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{0274D4CF-9932-47CC-8E89-54DC05B8F06E}" - ProjectSection(SolutionItems) = preProject - .vscode\launch.json = .vscode\launch.json - .vscode\tasks.json = .vscode\tasks.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E919DF0B-2607-4462-8FC0-5C98FE50F8C9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "icons", "icons", "{2B02E303-7CC6-4E15-97EE-DBE86B287553}" - ProjectSection(SolutionItems) = preProject - build\icons\imagesharp-logo-128.png = build\icons\imagesharp-logo-128.png - build\icons\imagesharp-logo-256.png = build\icons\imagesharp-logo-256.png - build\icons\imagesharp-logo-32.png = build\icons\imagesharp-logo-32.png - build\icons\imagesharp-logo-512.png = build\icons\imagesharp-logo-512.png - build\icons\imagesharp-logo-64.png = build\icons\imagesharp-logo-64.png - build\icons\imagesharp-logo.png = build\icons\imagesharp-logo.png - build\icons\imagesharp-logo.svg = build\icons\imagesharp-logo.svg - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}" ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props @@ -64,20 +41,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing", "src\ImageSharp.Drawing\ImageSharp.Drawing.csproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" ProjectSection(SolutionItems) = preProject tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeCoverage", "CodeCoverage", "{D4C5EC58-F8E6-4636-B9EE-C99D2578E5C6}" - ProjectSection(SolutionItems) = preProject - tests\CodeCoverage\CodeCoverage.cmd = tests\CodeCoverage\CodeCoverage.cmd - tests\CodeCoverage\packages.config = tests\CodeCoverage\packages.config - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Input", "Input", "{9DA226A1-8656-49A8-A58A-A8B5C081AD66}" @@ -354,9 +323,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\I EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml + EndProjectSection +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.shproj", "{68A8CC40-6AED-4E96-B524-31B1158FDEEA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}" EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2aa31a1f-142c-43f4-8687-09abca4b3a26}*SharedItemsImports = 5 + shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 @@ -378,18 +358,6 @@ Global {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.ActiveCfg = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.Build.0 = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.ActiveCfg = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.Build.0 = Debug|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.ActiveCfg = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.Build.0 = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.ActiveCfg = Release|Any CPU - {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -414,28 +382,25 @@ Global {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.ActiveCfg = Debug|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.Build.0 = Debug|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x86.ActiveCfg = Debug|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x86.Build.0 = Debug|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|Any CPU.Build.0 = Release|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x64.ActiveCfg = Release|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x64.Build.0 = Release|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x86.ActiveCfg = Release|Any CPU - {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x86.Build.0 = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {FBE8C1AD-5AEC-4514-9B64-091D8E145865} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} - {2B02E303-7CC6-4E15-97EE-DBE86B287553} = {E919DF0B-2607-4462-8FC0-5C98FE50F8C9} {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} - {2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} - {D4C5EC58-F8E6-4636-B9EE-C99D2578E5C6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {FA55F5DE-11A6-487D-ABA4-BC93A02717DD} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {9DA226A1-8656-49A8-A58A-A8B5C081AD66} = {FA55F5DE-11A6-487D-ABA4-BC93A02717DD} {1A82C5F6-90E0-4E97-BE16-A825C046B493} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} @@ -452,7 +417,9 @@ Global {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} + {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings deleted file mode 100644 index ece3dddb3c..0000000000 --- a/ImageSharp.sln.DotSettings +++ /dev/null @@ -1,393 +0,0 @@ - - <?xml version="1.0" encoding="utf-16"?> -<Profile name="StyleCop"> - <CSUpdateFileHeader>False</CSUpdateFileHeader> - <CSArrangeQualifiers>True</CSArrangeQualifiers> - <CSOptimizeUsings> - <OptimizeUsings>True</OptimizeUsings> - <EmbraceInRegion>False</EmbraceInRegion> - <RegionName></RegionName> - </CSOptimizeUsings> - <CSReformatCode>True</CSReformatCode> - <CSReorderTypeMembers>True</CSReorderTypeMembers> -</Profile> - StyleCop - public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async - Field, Property, Event, Method - True - True - True - True - True - True - True - True - True - NEXT_LINE_SHIFTED_2 - 1 - 1 - 1 - 1 - 1 - NEXT_LINE_SHIFTED_2 - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - NEXT_LINE_SHIFTED_2 - 1 - 1 - False - False - False - NEVER - False - False - NEVER - False - ALWAYS - False - True - ON_SINGLE_LINE - False - True - True - False - True - True - CHOP_IF_LONG - True - True - CHOP_IF_LONG - CHOP_IF_LONG - <?xml version="1.0" encoding="utf-16"?> -<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="P/Invoke classes called 'NativeMethods' (StyleCop)"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <Name Is=".*NativeMethods" /> - </And> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="DataMember serialisation classes (StyleCop)"> - <TypePattern.Match> - <And> - <Or> - <Kind Is="Field" /> - <Kind Is="Property" /> - </Or> - <HasAttribute Name="System.Runtime.Serialization.DataMemberAttribute" /> - </And> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="Default Pattern (StyleCop)" RemoveRegions="All"> - <Entry DisplayName="Constants"> - <Entry.Match> - <Kind Is="Constant" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Static fields"> - <Entry.Match> - <And> - <Kind Is="Field" /> - <Static /> - </And> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Readonly /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Fields"> - <Entry.Match> - <Kind Is="Field" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Readonly /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry Priority="200" DisplayName="Constructors and Destructors"> - <Entry.Match> - <Or> - <Kind Is="Constructor" /> - <Kind Is="Destructor" /> - </Or> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Kind Order="Constructor Destructor" /> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Delegates"> - <Entry.Match> - <Kind Is="Delegate" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Public events"> - <Entry.Match> - <And> - <Kind Is="Event" /> - <Access Is="Public" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Interface events"> - <Entry.Match> - <And> - <Kind Is="Event" /> - <ImplementsInterface /> - </And> - </Entry.Match> - <Entry.SortBy> - <ImplementsInterface Immediate="True" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Other events"> - <Entry.Match> - <Kind Is="Event" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Enums"> - <Entry.Match> - <Kind Is="Enum" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Interfaces"> - <Entry.Match> - <Kind Is="Interface" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Public properties"> - <Entry.Match> - <And> - <Kind Is="Property" /> - <Access Is="Public" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Interface properties"> - <Entry.Match> - <And> - <Kind Is="Property" /> - <ImplementsInterface /> - </And> - </Entry.Match> - <Entry.SortBy> - <ImplementsInterface Immediate="True" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Other properties"> - <Entry.Match> - <Kind Is="Property" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry Priority="1000" DisplayName="Public indexers"> - <Entry.Match> - <And> - <Kind Is="Indexer" /> - <Access Is="Public" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry Priority="1000" DisplayName="Interface indexers"> - <Entry.Match> - <And> - <Kind Is="Indexer" /> - <ImplementsInterface /> - </And> - </Entry.Match> - <Entry.SortBy> - <ImplementsInterface Immediate="True" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry Priority="1000" DisplayName="Other indexers"> - <Entry.Match> - <Kind Is="Indexer" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Public methods and operators"> - <Entry.Match> - <And> - <Or> - <Kind Is="Method" /> - <Kind Is="Operator" /> - </Or> - <Access Is="Public" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Interface methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <ImplementsInterface /> - </And> - </Entry.Match> - <Entry.SortBy> - <ImplementsInterface Immediate="True" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Other methods"> - <Entry.Match> - <Kind Is="Method" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Operators"> - <Entry.Match> - <Kind Is="Operator" /> - </Entry.Match> - <Entry.SortBy> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Static /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry Priority="600" DisplayName="Nested structs"> - <Entry.Match> - <Kind Is="Struct" /> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry Priority="700" DisplayName="Nested classes"> - <Entry.Match> - <Kind Is="Class" /> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Access Order="Public Internal ProtectedInternal Protected Private" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="All other members" /> - </TypePattern> -</Patterns> - False - True - // Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - - AC - DC - DCT - EOF - FDCT - IDCT - JPEG - MCU - PNG - RGB - RLE - XY - XYZ - $object$_On$event$ - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True - True - True - True - True - True - True - True - True - True - True - True - \ No newline at end of file diff --git a/README.md b/README.md index 078219183e..1e52039568 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ SixLabors.ImageSharp
+[![Build Status](https://img.shields.io/github/workflow/status/SixLabors/ImageSharp/Build/master)](https://github.com/SixLabors/ImageSharp/actions) +[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp) [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/LICENSE) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) @@ -35,7 +37,6 @@ Install stable releases via Nuget; development releases are available via MyGet. | Package Name | Release (NuGet) | Nightly (MyGet) | |--------------------------------|-----------------|-----------------| | `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) | -| `SixLabors.ImageSharp.Drawing` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Drawing.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Drawing/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Drawing.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Drawing) | ### Packages @@ -46,23 +47,15 @@ The **ImageSharp** library is made up of multiple packages: - Transform methods like Resize, Crop, Skew, Rotate - anything that alters the dimensions of the image - Non-transform methods like Gaussian Blur, Pixelate, Edge Detection - anything that maintains the original image dimensions -- **SixLabors.ImageSharp.Drawing** - - Brushes and various drawing algorithms, including drawing images - - Various vector drawing methods for drawing paths, polygons etc. - - Text drawing - -### Build Status - -| |Build Status|Code Coverage| -|-------------|:----------:|:-----------:| -|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| - ### Questions? - Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag. **Do not** open issues for questions! - Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening issues or pull requests! +### Code of Conduct +This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + ### API Our API is designed to be simple to consume. Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. @@ -76,7 +69,7 @@ using SixLabors.ImageSharp.PixelFormats; // Image.Load(string path) is a shortcut for our default type. // Other pixel formats use Image.Load(string path)) -using (Image image = Image.Load("foo.jpg")) +using (Image image = Image.Load("foo.jpg")) { image.Mutate(x => x .Resize(image.Width / 2, image.Height / 2) @@ -109,9 +102,9 @@ For more examples check out: If you prefer, you can compile ImageSharp yourself (please do and help!) -- Using [Visual Studio 2017](https://visualstudio.microsoft.com/vs/) +- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) - Make sure you have the latest version installed - - Make sure you have [the .NET Core 2.1 SDK](https://www.microsoft.com/net/core#windows) installed + - Make sure you have [the .NET Core 3.1 SDK](https://www.microsoft.com/net/core#windows) installed Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**: @@ -151,6 +144,7 @@ Core Team - [Dirk Lemstra](https://github.com/dlemstra) - [Anton Firsov](https://github.com/antonfirsov) - [Scott Williams](https://github.com/tocsoft) +- [Brian Popow](https://github.com/brianpopow) ### Backers diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 2cc5182d39..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: 1.0.0.{build} -image: Visual Studio 2017 - -# prevent the double build when a branch has an active PR -skip_branch_with_pr: true - -environment: - matrix: - - target_framework: netcoreapp2.1 - is_32bit: False - - - target_framework: netcoreapp2.1 - is_32bit: True - - - target_framework: net472 - is_32bit: False - - - target_framework: net472 - is_32bit: True - - - target_framework: net462 - is_32bit: False - - - target_framework: net462 - is_32bit: True - - #- target_framework: mono - # is_32bit: False - #- target_framework: mono - # is_32bit: True - #- target_framework: net47 - # is_32bit: False - #- target_framework: net47 - # is_32bit: True - -install: - - ps: | - if ($env:target_framework -eq "mono") { - if ($env:is_32bit -eq "True") { - cinst mono --x86 - } else { - cinst mono - } - } - -before_build: - - git submodule -q update --init - - cmd: dotnet --info - -build_script: -- cmd: build.cmd - -test_script: -- ps: .\run-tests.ps1 $env:target_framework $env:is_32bit - -after_test: - - cmd: appveyor PushArtifact "artifacts\SixLabors.ImageSharp.%APPVEYOR_BUILD_VERSION%.nupkg" - - cmd: appveyor PushArtifact "artifacts\SixLabors.ImageSharp.Drawing.%APPVEYOR_BUILD_VERSION%.nupkg" - -deploy: - # MyGet Deployment for builds & releases - - provider: NuGet - server: https://www.myget.org/F/sixlabors/api/v2/package - symbol_server: https://www.myget.org/F/sixlabors/symbols/api/v2/package - api_key: - secure: V/lEHP0UeMWIpWd0fiNlY2IgbCnJKQlGdRksECdJbOBdaE20Fl0RNL7WyqHe02o4 - artifact: /.*\.nupkg/ - on: - branch: master \ No newline at end of file diff --git a/build.cmd b/build.cmd deleted file mode 100644 index 6372b41253..0000000000 --- a/build.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@echo Off - -PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '.\build.ps1'" - -if not "%errorlevel%"=="0" goto failure - -:success -ECHO successfully built project -REM exit 0 -goto end - -:failure -ECHO failed to build. -REM exit -1 -goto end - -:end \ No newline at end of file diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index 215b551170..0000000000 --- a/build.ps1 +++ /dev/null @@ -1,122 +0,0 @@ - -# lets calulat the correct version here -$fallbackVersion = "1.0.0"; -$version = '' - -$tagRegex = '^v?(\d+\.\d+\.\d+)(-([a-zA-Z]+)\.?(\d*))?$' - -# we are running on the build server -$isVersionTag = $env:APPVEYOR_REPO_TAG_NAME -match $tagRegex - - if($isVersionTag) { - - Write-Debug "Building commit tagged with a compatable version number" - - $version = $matches[1] - $postTag = $matches[3] - $count = $matches[4] - Write-Debug "version number: ${version} post tag: ${postTag} count: ${count}" - if("$postTag" -ne ""){ - $version = "${version}-${postTag}" - } - if("$count" -ne ""){ - # for consistancy with previous releases we pad the counter to only 4 places - $padded = $count.Trim().Trim('0').PadLeft(4,"0"); - Write-Debug "count '$count', padded '${padded}'" - - $version = "${version}${padded}" - } - } - else { - - Write-Debug "Untagged" - $lastTag = (git tag --list --sort=-taggerdate) | Out-String - $list = $lastTag.Split("`n") - foreach ($tag in $list) { - - Write-Debug "testing ${tag}" - $tag = $tag.Trim(); - if($tag -match $tagRegex){ - Write-Debug "matched ${tag}" - $version = $matches[1]; - break; - } - } - - if("$version" -eq ""){ - $version = $fallbackVersion - Write-Debug "Failed to discover base version Fallback to '${version}'" - }else{ - - Write-Debug "Discovered base version from tags '${version}'" - } - - $buildNumber = $env:APPVEYOR_BUILD_NUMBER - - # build number replacement is padded to 6 places - $buildNumber = "$buildNumber".Trim().Trim('0').PadLeft(6,"0"); - if("$env:APPVEYOR_PULL_REQUEST_NUMBER" -ne ""){ - Write-Debug "building a PR" - - $prNumber = "$env:APPVEYOR_PULL_REQUEST_NUMBER".Trim().Trim('0').PadLeft(5,"0"); - # this is a PR - $version = "${version}-PullRequest${prNumber}${buildNumber}"; - }else{ - Write-Debug "building a branch commit" - - # this is a general branch commit - $branch = $env:APPVEYOR_REPO_BRANCH - - if("$branch" -eq ""){ - $branch = ((git rev-parse --abbrev-ref HEAD) | Out-String).Trim() - - if("$branch" -eq ""){ - $branch = "unknown" - } - } - - $branch = $branch.Replace("/","-").ToLower() - - if($branch.ToLower() -eq "master"){ - $branch = "dev" - } - - $version = "${version}-${branch}${buildNumber}"; - } - } - -if("$env:APPVEYOR_API_URL" -ne ""){ - # update appveyor build number for this build - Invoke-RestMethod -Method "PUT" ` - -Uri "${env:APPVEYOR_API_URL}api/build" ` - -Body "{version:'${version}'}" ` - -ContentType "application/json" -} - -Write-Host "Building version '${version}'" -dotnet restore /p:packageversion=$version /p:DisableImplicitNuGetFallbackFolder=true - -Write-Host "Building projects" -dotnet build -c Release /p:packageversion=$version - -if ($LASTEXITCODE ){ Exit $LASTEXITCODE } - -# -# TODO: DO WE NEED TO RUN TESTS IMPLICITLY? -# -# if ( $env:CI -ne "True") { -# cd ./tests/ImageSharp.Tests/ -# dotnet xunit -nobuild -c Release -f netcoreapp2.0 --fx-version 2.0.0 -# ./RunExtendedTests.cmd -# cd ../.. -# } -# - -if ($LASTEXITCODE ){ Exit $LASTEXITCODE } - -Write-Host "Packaging projects" -dotnet pack ./src/ImageSharp/ -c Release --output ../../artifacts --no-build /p:packageversion=$version -if ($LASTEXITCODE ){ Exit $LASTEXITCODE } - -dotnet pack ./src/ImageSharp.Drawing/ -c Release --output ../../artifacts --no-build /p:packageversion=$version -if ($LASTEXITCODE ){ Exit $LASTEXITCODE } diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png deleted file mode 100644 index 5c2079144f..0000000000 --- a/build/icons/imagesharp-logo-128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:148a268c589b628f5d0b5af0e86911a0b393c35b8b25233c71553657c88e0b96 -size 7568 diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png deleted file mode 100644 index e38807ae1e..0000000000 --- a/build/icons/imagesharp-logo-256.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e4b2ff72aef1979500cd130c28490a00be116bb833bc96ca30c85dc0596099c -size 15413 diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png deleted file mode 100644 index 273b171eb2..0000000000 --- a/build/icons/imagesharp-logo-32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:021c12313afbdc65f58bfea8c7b436d5c2102513bb63d9e64ee2b61a1344c56a -size 1799 diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png deleted file mode 100644 index 707dc9a35b..0000000000 --- a/build/icons/imagesharp-logo-512.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ae54ae0035df1f8f1459081e2f1d5cceda6f88cca6ec015d8c0209bf0d34edf -size 32534 diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png deleted file mode 100644 index 17577772eb..0000000000 --- a/build/icons/imagesharp-logo-64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92896854265693f28f9a503b9093cb2c9a4a9b329f310732efdd9c6f6c3761bc -size 3736 diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png deleted file mode 100644 index 707dc9a35b..0000000000 --- a/build/icons/imagesharp-logo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ae54ae0035df1f8f1459081e2f1d5cceda6f88cca6ec015d8c0209bf0d34edf -size 32534 diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg deleted file mode 100644 index 620287457a..0000000000 --- a/build/icons/imagesharp-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ci-build.ps1 b/ci-build.ps1 new file mode 100644 index 0000000000..17c6e6603b --- /dev/null +++ b/ci-build.ps1 @@ -0,0 +1,13 @@ +param( + [Parameter(Mandatory, Position = 0)] + [string]$version, + [Parameter(Mandatory = $true, Position = 1)] + [string]$targetFramework +) + +dotnet clean -c Release + +$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" + +# Building for a specific framework. +dotnet build -c Release -f $targetFramework /p:packageversion=$version /p:RepositoryUrl=$repositoryUrl diff --git a/ci-pack.ps1 b/ci-pack.ps1 new file mode 100644 index 0000000000..a4e846db95 --- /dev/null +++ b/ci-pack.ps1 @@ -0,0 +1,11 @@ +param( + [Parameter(Mandatory, Position = 0)] + [string]$version +) + +dotnet clean -c Release + +$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" + +# Building for packing and publishing. +dotnet pack -c Release --output "$PSScriptRoot/artifacts" /p:packageversion=$version /p:RepositoryUrl=$repositoryUrl diff --git a/ci-test.ps1 b/ci-test.ps1 new file mode 100644 index 0000000000..3915ae4ccd --- /dev/null +++ b/ci-test.ps1 @@ -0,0 +1,37 @@ +param( + [Parameter(Mandatory, Position = 0)] + [string]$os, + [Parameter(Mandatory, Position = 1)] + [string]$targetFramework, + [Parameter(Mandatory, Position = 2)] + [string]$platform, + [Parameter(Mandatory, Position = 3)] + [string]$codecov, + [Parameter(Position = 4)] + [string]$codecovProfile = 'Release' +) + +$netFxRegex = '^net\d+' + +if ($codecov -eq 'true') { + + # Allow toggling of profile to workaround any potential JIT errors caused by code injection. + dotnet clean -c $codecovProfile + dotnet test --collect "XPlat Code Coverage" --settings .\tests\coverlet.runsettings -c $codecovProfile -f $targetFramework /p:CodeCov=true +} +elseif ($platform -eq '-x86' -and $targetFramework -match $netFxRegex) { + + # xunit doesn't run on core with NET SDK 3.1+. + # xunit doesn't actually understand -x64 as an option. + # + # xunit requires explicit path. + Set-Location $env:XUNIT_PATH + + dotnet xunit --no-build -c Release -f $targetFramework ${fxVersion} $platform + + Set-Location $PSScriptRoot +} +else { + + dotnet test --no-build -c Release -f $targetFramework +} diff --git a/codecov.yml b/codecov.yml index ae6dd5f6bf..833fc0a51a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,11 @@ -ignore: - "src/ImageSharp/Common/Helpers/DebugGuard.cs" +# Documentation: https://docs.codecov.io/docs/codecov-yaml - \ No newline at end of file +codecov: + # Avoid "Missing base report" + # https://github.com/codecov/support/issues/363 + # https://docs.codecov.io/docs/comparing-commits + allow_coverage_offsets: true + + # Avoid Report Expired + # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports + max_report_age: off diff --git a/run-tests.ps1 b/run-tests.ps1 deleted file mode 100644 index 4aeaa14908..0000000000 --- a/run-tests.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -param( - [string]$targetFramework, - [string]$is32Bit = "False" -) - -if (!$targetFramework){ - Write-Host "run-tests.ps1 ERROR: targetFramework is undefined!" - exit 1 -} - -function VerifyPath($path, $errorMessage) { - if (!(Test-Path -Path $path)) { - Write-Host "run-tests.ps1 $errorMessage `n $xunitRunnerPath" - exit 1 - } -} - -function CheckSubmoduleStatus() { - $submoduleStatus = (git submodule status) | Out-String - # if the result string is empty, the command failed to run (we didn't capture the error stream) - if ($submoduleStatus) { - # git has been called successfully, what about the status? - if (($submoduleStatus -match "\-") -or ($submoduleStatus -match "\(\(null\)\)")) - { - # submodule has not been initialized! - return 2; - } - elseif ($submoduleStatus -match "\+") - { - # submodule is not synced: - return 1; - } - else { - # everything fine: - return 0; - } - } else { - # git call failed, so we should warn - return 3; - } -} - - -if ( ($targetFramework -eq "netcoreapp2.1") -and ($env:CI -eq "True") -and ($is32Bit -ne "True")) { - # We execute CodeCoverage.cmd only for one specific job on CI (netcoreapp2.1 + 64bit ) - $testRunnerCmd = ".\tests\CodeCoverage\CodeCoverage.cmd" -} -elseif ($targetFramework -eq "mono") { - $testDllPath = "$PSScriptRoot\tests\ImageSharp.Tests\bin\Release\net462\SixLabors.ImageSharp.Tests.dll" - VerifyPath($testDllPath, "test dll missing:") - - $xunitRunnerPath = "${env:HOMEPATH}\.nuget\packages\xunit.runner.console\2.3.1\tools\net452\" - - VerifyPath($xunitRunnerPath, "xunit console runner is missing on path:") - - cd "$xunitRunnerPath" - - if ($is32Bit -ne "True") { - $monoPath = "${env:PROGRAMFILES}\Mono\bin\mono.exe" - } - else { - $monoPath = "${env:ProgramFiles(x86)}\Mono\bin\mono.exe" - } - - VerifyPath($monoPath, "mono runtime missing:") - - $testRunnerCmd = "& `"${monoPath}`" .\xunit.console.exe `"${testDllPath}`"" -} -else { - cd .\tests\ImageSharp.Tests - $xunitArgs = "-nobuild -c Release -framework $targetFramework" - - if ($targetFramework -eq "netcoreapp2.1") { - # There were issues matching the correct installed runtime if we do not specify it explicitly: - $xunitArgs += " --fx-version 2.1.0" - } - - if ($is32Bit -eq "True") { - $xunitArgs += " -x86" - } - - $testRunnerCmd = "dotnet xunit $xunitArgs" -} - -Write-Host "running:" -Write-Host $testRunnerCmd -Write-Host "..." - -Invoke-Expression $testRunnerCmd - -cd $PSScriptRoot - -$exitCodeOfTests = $LASTEXITCODE; - -if (0 -ne ([int]$exitCodeOfTests)) { - # check submodule status - $submoduleStatus = CheckSubmoduleStatus - if ([int]$submoduleStatus -eq 1) { - # not synced - Write-Host -ForegroundColor Yellow "Check if submodules are up to date. You can use 'git submodule update' to fix this"; - } elseif ($submoduleStatus -eq 2) { - # not initialized - Write-Host -ForegroundColor Yellow "Check if submodules are initialized. You can run 'git submodule init' to initialize them." - } elseif ($submoduleStatus -eq 3) { - # git not found, maybe submodules not synced? - Write-Host -ForegroundColor Yellow "Could not check if submodules are initialized correctly. Maybe git is not installed?" - } else { - #Write-Host "Submodules are up to date"; - } -} - -exit $exitCodeOfTests diff --git a/shared-infrastructure b/shared-infrastructure index faf84e44ec..ea561c249b 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit faf84e44ec90e8a42a7271bcd04fea76279efb08 +Subproject commit ea561c249ba86352fe3b69e612b8072f3652eacb diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6fbbb7c916..a78a75d428 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -22,20 +22,18 @@ true - - - + + true + + - - - - + + + - - - + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index c0e01ae586..d7171aa0f6 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -50,6 +50,26 @@ - + + + + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj deleted file mode 100644 index 5a53d3e78b..0000000000 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - SixLabors.ImageSharp.Drawing - SixLabors.ImageSharp.Drawing - An extension to ImageSharp that allows the drawing of images, paths, and text. - SixLabors.ImageSharp.Drawing - Image Draw Shape Path Font - SixLabors.ImageSharp - netcoreapp2.1;netstandard1.3;netstandard2.0 - - - - - $(DefineConstants);SUPPORTS_MATHF - - - - $(DefineConstants);SUPPORTS_HASHCODE - - - - - - - - - - - - diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings deleted file mode 100644 index a728b54979..0000000000 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Primitives/Region.cs b/src/ImageSharp.Drawing/Primitives/Region.cs deleted file mode 100644 index 27f039f122..0000000000 --- a/src/ImageSharp.Drawing/Primitives/Region.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Primitives -{ - /// - /// Represents a region of an image. - /// - public abstract class Region - { - /// - /// Gets the maximum number of intersections to could be returned. - /// - public abstract int MaxIntersections { get; } - - /// - /// Gets the bounding box that entirely surrounds this region. - /// - /// - /// This should always contains all possible points returned from . - /// - public abstract Rectangle Bounds { get; } - - /// - /// Scans the X axis for intersections at the Y axis position. - /// - /// The position along the y axis to find intersections. - /// The buffer. - /// A instance in the context of the caller. - /// The number of intersections found. - public abstract int Scan(float y, Span buffer, Configuration configuration); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Primitives/ShapePath.cs b/src/ImageSharp.Drawing/Primitives/ShapePath.cs deleted file mode 100644 index a4fef66a67..0000000000 --- a/src/ImageSharp.Drawing/Primitives/ShapePath.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Primitives -{ - /// - /// A mapping between a and a region. - /// - internal class ShapePath : ShapeRegion - { - /// - /// Initializes a new instance of the class. - /// - /// The shape. - /// The pen to apply to the shape. - public ShapePath(IPath shape, IPen pen) - : base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern)) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs b/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs deleted file mode 100644 index f4a6458206..0000000000 --- a/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Primitives -{ - /// - /// A mapping between a and a region. - /// - internal class ShapeRegion : Region - { - /// - /// Initializes a new instance of the class. - /// - /// The shape. - public ShapeRegion(IPath shape) - { - this.Shape = shape.AsClosedPath(); - int left = (int)MathF.Floor(shape.Bounds.Left); - int top = (int)MathF.Floor(shape.Bounds.Top); - - int right = (int)MathF.Ceiling(shape.Bounds.Right); - int bottom = (int)MathF.Ceiling(shape.Bounds.Bottom); - this.Bounds = Rectangle.FromLTRB(left, top, right, bottom); - } - - /// - /// Gets the fillable shape - /// - public IPath Shape { get; } - - /// - public override int MaxIntersections => this.Shape.MaxIntersections; - - /// - public override Rectangle Bounds { get; } - - /// - public override int Scan(float y, Span buffer, Configuration configuration) - { - var start = new PointF(this.Bounds.Left - 1, y); - var end = new PointF(this.Bounds.Right + 1, y); - - using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(buffer.Length)) - { - Span innerBuffer = tempBuffer.GetSpan(); - int count = this.Shape.FindIntersections(start, end, innerBuffer); - - for (int i = 0; i < count; i++) - { - buffer[i] = innerBuffer[i].X; - } - - return count; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs deleted file mode 100644 index 7e75d7effc..0000000000 --- a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// primitive that converts a point in to a color for discovering the fill color based on an implementation - /// - /// The pixel format. - /// - public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The target. - /// The options. - internal BrushApplicator(ImageFrame target, GraphicsOptions options) - { - this.Target = target; - this.Options = options; - this.Blender = PixelOperations.Instance.GetPixelBlender(options); - } - - /// - /// Gets the blender - /// - internal PixelBlender Blender { get; } - - /// - /// Gets the destination - /// - protected ImageFrame Target { get; } - - /// - /// Gets the blend percentage - /// - protected GraphicsOptions Options { get; } - - /// - /// Gets the color for a single pixel. - /// - /// The x coordinate. - /// The y coordinate. - /// The a that should be applied to the pixel. - internal abstract TPixel this[int x, int y] { get; } - - /// - public abstract void Dispose(); - - /// - /// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush. - /// - /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. - /// The x position in the target pixel space that the start of the scanline data corresponds to. - /// The y position in the target pixel space that whole scanline corresponds to. - /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. - internal virtual void Apply(Span scanline, int x, int y) - { - MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; - - using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) - using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) - { - Span amountSpan = amountBuffer.GetSpan(); - Span overlaySpan = overlay.GetSpan(); - - for (int i = 0; i < scanline.Length; i++) - { - if (this.Options.BlendPercentage < 1) - { - amountSpan[i] = scanline[i] * this.Options.BlendPercentage; - } - else - { - amountSpan[i] = scanline[i]; - } - - overlaySpan[i] = this[x + i, y]; - } - - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(this.Target.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan); - } - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Brushes.cs b/src/ImageSharp.Drawing/Processing/Brushes.cs deleted file mode 100644 index bd10e90c68..0000000000 --- a/src/ImageSharp.Drawing/Processing/Brushes.cs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// A collection of methods for creating generic brushes. - /// - /// A New - public static class Brushes - { - /// - /// Percent10 Hatch Pattern - /// - /// ---> x axis - /// ^ - /// | y - axis - /// | - /// see PatternBrush for details about how to make new patterns work - private static readonly bool[,] Percent10Pattern = - { - { true, false, false, false }, - { false, false, false, false }, - { false, false, true, false }, - { false, false, false, false } - }; - - /// - /// Percent20 pattern. - /// - private static readonly bool[,] Percent20Pattern = - { - { true, false, false, false }, - { false, false, true, false }, - { true, false, false, false }, - { false, false, true, false } - }; - - /// - /// Horizontal Hatch Pattern - /// - private static readonly bool[,] HorizontalPattern = - { - { false }, - { true }, - { false }, - { false } - }; - - /// - /// Min Pattern - /// - private static readonly bool[,] MinPattern = - { - { false }, - { false }, - { false }, - { true } - }; - - /// - /// Vertical Pattern - /// - private static readonly bool[,] VerticalPattern = - { - { false, true, false, false }, - }; - - /// - /// Forward Diagonal Pattern - /// - private static readonly bool[,] ForwardDiagonalPattern = - { - { false, false, false, true }, - { false, false, true, false }, - { false, true, false, false }, - { true, false, false, false } - }; - - /// - /// Backward Diagonal Pattern - /// - private static readonly bool[,] BackwardDiagonalPattern = - { - { true, false, false, false }, - { false, true, false, false }, - { false, false, true, false }, - { false, false, false, true } - }; - - /// - /// Create as brush that will paint a solid color - /// - /// The color. - /// A New - public static SolidBrush Solid(Color color) => new SolidBrush(color); - - /// - /// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// A New - public static PatternBrush Percent10(Color foreColor) => - new PatternBrush(foreColor, Color.Transparent, Percent10Pattern); - - /// - /// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush Percent10(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, Percent10Pattern); - - /// - /// Create as brush that will paint a Percent20 Hatch Pattern with the specified foreground color and a - /// transparent background. - /// - /// Color of the foreground. - /// A New - public static PatternBrush Percent20(Color foreColor) => - new PatternBrush(foreColor, Color.Transparent, Percent20Pattern); - - /// - /// Create as brush that will paint a Percent20 Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush Percent20(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, Percent20Pattern); - - /// - /// Create as brush that will paint a Horizontal Hatch Pattern with the specified foreground color and a - /// transparent background. - /// - /// Color of the foreground. - /// A New - public static PatternBrush Horizontal(Color foreColor) => - new PatternBrush(foreColor, Color.Transparent, HorizontalPattern); - - /// - /// Create as brush that will paint a Horizontal Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush Horizontal(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, HorizontalPattern); - - /// - /// Create as brush that will paint a Min Hatch Pattern with the specified foreground color and a - /// transparent background. - /// - /// Color of the foreground. - /// A New - public static PatternBrush Min(Color foreColor) => new PatternBrush(foreColor, Color.Transparent, MinPattern); - - /// - /// Create as brush that will paint a Min Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush Min(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, MinPattern); - - /// - /// Create as brush that will paint a Vertical Hatch Pattern with the specified foreground color and a - /// transparent background. - /// - /// Color of the foreground. - /// A New - public static PatternBrush Vertical(Color foreColor) => - new PatternBrush(foreColor, Color.Transparent, VerticalPattern); - - /// - /// Create as brush that will paint a Vertical Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush Vertical(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, VerticalPattern); - - /// - /// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified foreground color and a - /// transparent background. - /// - /// Color of the foreground. - /// A New - public static PatternBrush ForwardDiagonal(Color foreColor) => - new PatternBrush(foreColor, Color.Transparent, ForwardDiagonalPattern); - - /// - /// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush ForwardDiagonal(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, ForwardDiagonalPattern); - - /// - /// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified foreground color and a - /// transparent background. - /// - /// Color of the foreground. - /// A New - public static PatternBrush BackwardDiagonal(Color foreColor) => - new PatternBrush(foreColor, Color.Transparent, BackwardDiagonalPattern); - - /// - /// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified colors - /// - /// Color of the foreground. - /// Color of the background. - /// A New - public static PatternBrush BackwardDiagonal(Color foreColor, Color backColor) => - new PatternBrush(foreColor, backColor, BackwardDiagonalPattern); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/ColorStop.cs b/src/ImageSharp.Drawing/Processing/ColorStop.cs deleted file mode 100644 index 21c82b63ff..0000000000 --- a/src/ImageSharp.Drawing/Processing/ColorStop.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// A struct that defines a single color stop. - /// - [DebuggerDisplay("ColorStop({Ratio} -> {Color}")] - public readonly struct ColorStop - { - /// - /// Initializes a new instance of the struct. - /// - /// Where should it be? 0 is at the start, 1 at the end of the Gradient. - /// What color should be used at that point? - public ColorStop(float ratio, in Color color) - { - this.Ratio = ratio; - this.Color = color; - } - - /// - /// Gets the point along the defined gradient axis. - /// - public float Ratio { get; } - - /// - /// Gets the color to be used. - /// - public Color Color { get; } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawingHelpers.cs b/src/ImageSharp.Drawing/Processing/DrawingHelpers.cs deleted file mode 100644 index 25a8204f2a..0000000000 --- a/src/ImageSharp.Drawing/Processing/DrawingHelpers.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - internal static class DrawingHelpers - { - /// - /// Convert a to a of the given pixel type. - /// - public static DenseMatrix ToPixelMatrix(this DenseMatrix colorMatrix, Configuration configuration) - where TPixel : struct, IPixel - { - var result = new DenseMatrix(colorMatrix.Columns, colorMatrix.Rows); - Color.ToPixel(configuration, colorMatrix.Span, result.Span); - return result; - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs deleted file mode 100644 index 91da332a16..0000000000 --- a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Gradient Brush with elliptic shape. - /// The ellipse is defined by a center point, - /// a point on the longest extension of the ellipse and - /// the ratio between longest and shortest extension. - /// - public sealed class EllipticGradientBrush : GradientBrush - { - private readonly PointF center; - - private readonly PointF referenceAxisEnd; - - private readonly float axisRatio; - - /// - /// The center of the elliptical gradient and 0 for the color stops. - /// The end point of the reference axis of the ellipse. - /// - /// The ratio of the axis widths. - /// The second axis' is perpendicular to the reference axis and - /// it's length is the reference axis' length multiplied by this factor. - /// - /// Defines how the colors of the gradients are repeated. - /// the color stops as defined in base class. - public EllipticGradientBrush( - PointF center, - PointF referenceAxisEnd, - float axisRatio, - GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) - : base(repetitionMode, colorStops) - { - this.center = center; - this.referenceAxisEnd = referenceAxisEnd; - this.axisRatio = axisRatio; - } - - /// - public override BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) => - new RadialGradientBrushApplicator( - source, - options, - this.center, - this.referenceAxisEnd, - this.axisRatio, - this.ColorStops, - this.RepetitionMode); - - /// - private sealed class RadialGradientBrushApplicator : GradientBrushApplicator - where TPixel : struct, IPixel - { - private readonly PointF center; - - private readonly PointF referenceAxisEnd; - - private readonly float axisRatio; - - private readonly double rotation; - - private readonly float referenceRadius; - - private readonly float secondRadius; - - private readonly float cosRotation; - - private readonly float sinRotation; - - private readonly float secondRadiusSquared; - - private readonly float referenceRadiusSquared; - - /// - /// Initializes a new instance of the class. - /// - /// The target image - /// The options - /// Center of the ellipse - /// Point on one angular points of the ellipse. - /// - /// Ratio of the axis length's. Used to determine the length of the second axis, - /// the first is defined by and . - /// Definition of colors - /// Defines how the gradient colors are repeated. - public RadialGradientBrushApplicator( - ImageFrame target, - GraphicsOptions options, - PointF center, - PointF referenceAxisEnd, - float axisRatio, - ColorStop[] colorStops, - GradientRepetitionMode repetitionMode) - : base(target, options, colorStops, repetitionMode) - { - this.center = center; - this.referenceAxisEnd = referenceAxisEnd; - this.axisRatio = axisRatio; - this.rotation = this.AngleBetween( - this.center, - new PointF(this.center.X + 1, this.center.Y), - this.referenceAxisEnd); - this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); - this.secondRadius = this.referenceRadius * this.axisRatio; - - this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius; - this.secondRadiusSquared = this.secondRadius * this.secondRadius; - - this.sinRotation = (float)Math.Sin(this.rotation); - this.cosRotation = (float)Math.Cos(this.rotation); - } - - /// - public override void Dispose() - { - } - - /// - protected override float PositionOnGradient(float xt, float yt) - { - float x0 = xt - this.center.X; - float y0 = yt - this.center.Y; - - float x = (x0 * this.cosRotation) - (y0 * this.sinRotation); - float y = (x0 * this.sinRotation) + (y0 * this.cosRotation); - - float xSquared = x * x; - float ySquared = y * y; - - var inBoundaryChecker = (xSquared / this.referenceRadiusSquared) - + (ySquared / this.secondRadiusSquared); - - return inBoundaryChecker; - } - - private float AngleBetween(PointF junction, PointF a, PointF b) - { - var vA = a - junction; - var vB = b - junction; - return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X); - } - - private float DistanceBetween( - PointF p1, - PointF p2) - { - float dX = p1.X - p2.X; - float dXsquared = dX * dX; - - float dY = p1.Y - p2.Y; - float dYsquared = dY * dY; - return MathF.Sqrt(dXsquared + dYsquared); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs deleted file mode 100644 index 7660e72255..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of Bezier paths to the type. - /// - public static class DrawBezierExtensions - { - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - float thickness, - params PointF[] points) => - source.Draw(options, new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The brush. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - IBrush brush, - float thickness, - params PointF[] points) => - source.Draw(new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The color. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - Color color, - float thickness, - params PointF[] points) => - source.DrawBeziers(new SolidBrush(color), thickness, points); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The options. - /// The color. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float thickness, - params PointF[] points) => - source.DrawBeziers(options, new SolidBrush(color), thickness, points); - - /// - /// Draws the provided points as an open Bezier path with the supplied pen - /// - /// The image this method extends. - /// The options. - /// The pen. - /// The points. - /// The . - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - GraphicsOptions options, - IPen pen, - params PointF[] points) => - source.Draw(options, pen, new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path with the supplied pen - /// - /// The image this method extends. - /// The pen. - /// The points. - /// The . - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - IPen pen, - params PointF[] points) => - source.Draw(pen, new Path(new CubicBezierLineSegment(points))); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs deleted file mode 100644 index 981cf1bef4..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of images to the type. - /// - public static class DrawImageExtensions - { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - Point.Empty, - GraphicsOptions.Default.ColorBlendingMode, - GraphicsOptions.Default.AlphaCompositionMode, - opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The blending mode. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - PixelColorBlendingMode colorBlending, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - Point.Empty, - colorBlending, - GraphicsOptions.Default.AlphaCompositionMode, - opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The color blending mode. - /// The alpha composition mode. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) => - source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The options, including the blending type and blending amount. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - GraphicsOptions options) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - Point.Empty, - options.ColorBlendingMode, - options.AlphaCompositionMode, - options.BlendPercentage)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - location, - GraphicsOptions.Default.ColorBlendingMode, - GraphicsOptions.Default.AlphaCompositionMode, - opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The color blending to apply. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - PixelColorBlendingMode colorBlending, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - location, - colorBlending, - GraphicsOptions.Default.AlphaCompositionMode, - opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The color blending to apply. - /// The alpha composition mode. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) => - source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The options containing the blend mode and opacity. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - GraphicsOptions options) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - location, - options.ColorBlendingMode, - options.AlphaCompositionMode, - options.BlendPercentage)); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs deleted file mode 100644 index 98e8fdc594..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of lines to the type. - /// - public static class DrawLineExtensions - { - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawLines( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - float thickness, - params PointF[] points) => - source.Draw(options, new Pen(brush, thickness), new Path(new LinearLineSegment(points))); - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The brush. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawLines( - this IImageProcessingContext source, - IBrush brush, - float thickness, - params PointF[] points) => - source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points))); - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The color. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawLines( - this IImageProcessingContext source, - Color color, - float thickness, - params PointF[] points) => - source.DrawLines(new SolidBrush(color), thickness, points); - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The image this method extends. - /// The options. - /// The color. - /// The thickness. - /// The points. - /// The .> - public static IImageProcessingContext DrawLines( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float thickness, - params PointF[] points) => - source.DrawLines(options, new SolidBrush(color), thickness, points); - - /// - /// Draws the provided Points as an open Linear path with the supplied pen - /// - /// The image this method extends. - /// The options. - /// The pen. - /// The points. - /// The . - public static IImageProcessingContext DrawLines( - this IImageProcessingContext source, - GraphicsOptions options, - IPen pen, - params PointF[] points) => - source.Draw(options, pen, new Path(new LinearLineSegment(points))); - - /// - /// Draws the provided Points as an open Linear path with the supplied pen - /// - /// The image this method extends. - /// The pen. - /// The points. - /// The . - public static IImageProcessingContext DrawLines( - this IImageProcessingContext source, - IPen pen, - params PointF[] points) => - source.Draw(pen, new Path(new LinearLineSegment(points))); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs deleted file mode 100644 index a68b69a444..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of collections of polygon outlines to the type. - /// - public static class DrawPathCollectionExtensions - { - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The image this method extends. - /// The options. - /// The pen. - /// The paths. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - IPen pen, - IPathCollection paths) - { - foreach (IPath path in paths) - { - source.Draw(options, pen, path); - } - - return source; - } - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The image this method extends. - /// The pen. - /// The paths. - /// The . - public static IImageProcessingContext - Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) => - source.Draw(GraphicsOptions.Default, pen, paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The thickness. - /// The shapes. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - float thickness, - IPathCollection paths) => - source.Draw(options, new Pen(brush, thickness), paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The brush. - /// The thickness. - /// The paths. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - IBrush brush, - float thickness, - IPathCollection paths) => - source.Draw(new Pen(brush, thickness), paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The thickness. - /// The paths. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float thickness, - IPathCollection paths) => - source.Draw(options, new SolidBrush(color), thickness, paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The color. - /// The thickness. - /// The paths. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Color color, - float thickness, - IPathCollection paths) => - source.Draw(new SolidBrush(color), thickness, paths); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs deleted file mode 100644 index dfe30f6a3c..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of polygon outlines to the type. - /// - public static class DrawPathExtensions - { - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The image this method extends. - /// The options. - /// The pen. - /// The path. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - IPen pen, - IPath path) => - source.Fill(options, pen.StrokeFill, new ShapePath(path, pen)); - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The image this method extends. - /// The pen. - /// The path. - /// The . - public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) => - source.Draw(GraphicsOptions.Default, pen, path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The thickness. - /// The shape. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - float thickness, - IPath path) => - source.Draw(options, new Pen(brush, thickness), path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The brush. - /// The thickness. - /// The path. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - IBrush brush, - float thickness, - IPath path) => - source.Draw(new Pen(brush, thickness), path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The thickness. - /// The path. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float thickness, - IPath path) => - source.Draw(options, new SolidBrush(color), thickness, path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The color. - /// The thickness. - /// The path. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Color color, - float thickness, - IPath path) => - source.Draw(new SolidBrush(color), thickness, path); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs deleted file mode 100644 index 86d8e9e2e2..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of closed linear polygons to the type. - /// - public static class DrawPolygonExtensions - { - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - float thickness, - params PointF[] points) => - source.Draw(options, new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The brush. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - IBrush brush, - float thickness, - params PointF[] points) => - source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The color. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - Color color, - float thickness, - params PointF[] points) => - source.DrawPolygon(new SolidBrush(color), thickness, points); - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The thickness. - /// The points. - /// The . - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float thickness, - params PointF[] points) => - source.DrawPolygon(options, new SolidBrush(color), thickness, points); - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided Pen. - /// - /// The image this method extends. - /// The pen. - /// The points. - /// The . - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - IPen pen, - params PointF[] points) => - source.Draw(GraphicsOptions.Default, pen, new Polygon(new LinearLineSegment(points))); - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided Pen. - /// - /// The image this method extends. - /// The options. - /// The pen. - /// The points. - /// The . - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - GraphicsOptions options, - IPen pen, - params PointF[] points) => - source.Draw(options, pen, new Polygon(new LinearLineSegment(points))); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs deleted file mode 100644 index da78ab2ecc..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of rectangles to the type. - /// - public static class DrawRectangleExtensions - { - /// - /// Draws the outline of the rectangle with the provided pen. - /// - /// The image this method extends. - /// The options. - /// The pen. - /// The shape. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - IPen pen, - RectangleF shape) => - source.Draw(options, pen, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); - - /// - /// Draws the outline of the rectangle with the provided pen. - /// - /// The image this method extends. - /// The pen. - /// The shape. - /// The . - public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) => - source.Draw(GraphicsOptions.Default, pen, shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The thickness. - /// The shape. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - float thickness, - RectangleF shape) => - source.Draw(options, new Pen(brush, thickness), shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The brush. - /// The thickness. - /// The shape. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - IBrush brush, - float thickness, - RectangleF shape) => - source.Draw(new Pen(brush, thickness), shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The thickness. - /// The shape. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float thickness, - RectangleF shape) => - source.Draw(options, new SolidBrush(color), thickness, shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The image this method extends. - /// The color. - /// The thickness. - /// The shape. - /// The . - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Color color, - float thickness, - RectangleF shape) => - source.Draw(new SolidBrush(color), thickness, shape); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs deleted file mode 100644 index 05cd3a1ae6..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Fonts; -using SixLabors.ImageSharp.Processing.Processors.Text; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of text to the type. - /// - public static class DrawTextExtensions - { - /// - /// Draws the text onto the the image filled via the brush. - /// - /// The image this method extends. - /// The text. - /// The font. - /// The color. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - Color color, - PointF location) => - source.DrawText(TextGraphicsOptions.Default, text, font, color, location); - - /// - /// Draws the text onto the the image filled via the brush. - /// - /// The image this method extends. - /// The options. - /// The text. - /// The font. - /// The color. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - TextGraphicsOptions options, - string text, - Font font, - Color color, - PointF location) => - source.DrawText(options, text, font, Brushes.Solid(color), null, location); - - /// - /// Draws the text onto the the image filled via the brush. - /// - /// The image this method extends. - /// The text. - /// The font. - /// The brush. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - IBrush brush, - PointF location) => - source.DrawText(TextGraphicsOptions.Default, text, font, brush, location); - - /// - /// Draws the text onto the the image filled via the brush. - /// - /// The image this method extends. - /// The options. - /// The text. - /// The font. - /// The brush. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - TextGraphicsOptions options, - string text, - Font font, - IBrush brush, - PointF location) => - source.DrawText(options, text, font, brush, null, location); - - /// - /// Draws the text onto the the image outlined via the pen. - /// - /// The image this method extends. - /// The text. - /// The font. - /// The pen. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - IPen pen, - PointF location) => - source.DrawText(TextGraphicsOptions.Default, text, font, pen, location); - - /// - /// Draws the text onto the the image outlined via the pen. - /// - /// The image this method extends. - /// The options. - /// The text. - /// The font. - /// The pen. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - TextGraphicsOptions options, - string text, - Font font, - IPen pen, - PointF location) => - source.DrawText(options, text, font, null, pen, location); - - /// - /// Draws the text onto the the image filled via the brush then outlined via the pen. - /// - /// The image this method extends. - /// The text. - /// The font. - /// The brush. - /// The pen. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - IBrush brush, - IPen pen, - PointF location) => - source.DrawText(TextGraphicsOptions.Default, text, font, brush, pen, location); - - /// - /// Draws the text using the default resolution of 72dpi onto the the image filled via the brush then outlined via the pen. - /// - /// The image this method extends. - /// The options. - /// The text. - /// The font. - /// The brush. - /// The pen. - /// The location. - /// - /// The . - /// - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - TextGraphicsOptions options, - string text, - Font font, - IBrush brush, - IPen pen, - PointF location) => - source.ApplyProcessor(new DrawTextProcessor(options, text, font, brush, pen, location)); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs deleted file mode 100644 index 5de9c6d4ed..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the filling of polygons with various brushes to the type. - /// - public static class FillPathBuilderExtensions - { - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The graphics options. - /// The brush. - /// The shape. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - Action path) - { - var pb = new PathBuilder(); - path(pb); - - return source.Fill(options, brush, pb.Build()); - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The brush. - /// The path. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - IBrush brush, - Action path) => - source.Fill(GraphicsOptions.Default, brush, path); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The path. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - Action path) => - source.Fill(options, new SolidBrush(color), path); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The color. - /// The path. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Color color, - Action path) => - source.Fill(new SolidBrush(color), path); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs deleted file mode 100644 index 776e1f7e4e..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the filling of collections of polygon outlines to the type. - /// - public static class FillPathCollectionExtensions - { - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The graphics options. - /// The brush. - /// The shapes. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - IPathCollection paths) - { - foreach (IPath s in paths) - { - source.Fill(options, brush, s); - } - - return source; - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The brush. - /// The paths. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - IBrush brush, - IPathCollection paths) => - source.Fill(GraphicsOptions.Default, brush, paths); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The paths. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - IPathCollection paths) => - source.Fill(options, new SolidBrush(color), paths); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The color. - /// The paths. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Color color, - IPathCollection paths) => - source.Fill(new SolidBrush(color), paths); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs deleted file mode 100644 index 718016a9e6..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the filling of polygon outlines to the type. - /// - public static class FillPathExtensions - { - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The graphics options. - /// The brush. - /// The shape. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - IPath path) => - source.Fill(options, brush, new ShapeRegion(path)); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The image this method extends. - /// The brush. - /// The path. - /// The . - public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) => - source.Fill(GraphicsOptions.Default, brush, new ShapeRegion(path)); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush.. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The path. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - IPath path) => - source.Fill(options, new SolidBrush(color), path); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush.. - /// - /// The image this method extends. - /// The color. - /// The path. - /// The . - public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, IPath path) => - source.Fill(new SolidBrush(color), path); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs deleted file mode 100644 index 9262c8baad..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the filling of closed linear polygons to the type. - /// - public static class FillPolygonExtensions - { - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The points. - /// The . - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - params PointF[] points) => - source.Fill(options, brush, new Polygon(new LinearLineSegment(points))); - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The image this method extends. - /// The brush. - /// The points. - /// The . - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - IBrush brush, - params PointF[] points) => - source.Fill(brush, new Polygon(new LinearLineSegment(points))); - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The image this method extends. - /// The options. - /// The color. - /// The points. - /// The . - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - params PointF[] points) => - source.Fill(options, new SolidBrush(color), new Polygon(new LinearLineSegment(points))); - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The image this method extends. - /// The color. - /// The points. - /// The . - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - Color color, - params PointF[] points) => - source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs deleted file mode 100644 index cfe37deb2c..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the filling of rectangles to the type. - /// - public static class FillRectangleExtensions - { - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The image this method extends. - /// The options. - /// The brush. - /// The shape. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - RectangleF shape) => - source.Fill(options, brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); - - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The image this method extends. - /// The brush. - /// The shape. - /// The . - public static IImageProcessingContext - Fill(this IImageProcessingContext source, IBrush brush, RectangleF shape) => - source.Fill(brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); - - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The shape. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - RectangleF shape) => - source.Fill(options, new SolidBrush(color), shape); - - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The image this method extends. - /// The color. - /// The shape. - /// The . - public static IImageProcessingContext - Fill(this IImageProcessingContext source, Color color, RectangleF shape) => - source.Fill(new SolidBrush(color), shape); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs deleted file mode 100644 index 294e575140..0000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors.Drawing; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the filling of regions with various brushes to the type. - /// - public static class FillRegionExtensions - { - /// - /// Flood fills the image with the specified brush. - /// - /// The image this method extends. - /// The details how to fill the region of interest. - /// The . - public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) => - source.Fill(GraphicsOptions.Default, brush); - - /// - /// Flood fills the image with the specified color. - /// - /// The image this method extends. - /// The color. - /// The . - public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color) => - source.Fill(new SolidBrush(color)); - - /// - /// Flood fills the image with in the region with the specified brush. - /// - /// The image this method extends. - /// The brush. - /// The region. - /// The . - public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) => - source.Fill(GraphicsOptions.Default, brush, region); - - /// - /// Flood fills the image with in the region with the specified color. - /// - /// The image this method extends. - /// The options. - /// The color. - /// The region. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - Region region) => - source.Fill(options, new SolidBrush(color), region); - - /// - /// Flood fills the image with in the region with the specified color. - /// - /// The image this method extends. - /// The color. - /// The region. - /// The . - public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, Region region) => - source.Fill(new SolidBrush(color), region); - - /// - /// Flood fills the image with in the region with the specified brush. - /// - /// The image this method extends. - /// The graphics options. - /// The brush. - /// The region. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush, - Region region) => - source.ApplyProcessor(new FillRegionProcessor(brush, region, options)); - - /// - /// Flood fills the image with the specified brush. - /// - /// The image this method extends. - /// The graphics options. - /// The details how to fill the region of interest. - /// The . - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - GraphicsOptions options, - IBrush brush) => - source.ApplyProcessor(new FillProcessor(brush, options)); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/GradientBrush.cs b/src/ImageSharp.Drawing/Processing/GradientBrush.cs deleted file mode 100644 index 9826748c46..0000000000 --- a/src/ImageSharp.Drawing/Processing/GradientBrush.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Base class for Gradient brushes - /// - public abstract class GradientBrush : IBrush - { - /// - /// Defines how the colors are repeated beyond the interval [0..1] - /// The gradient colors. - protected GradientBrush( - GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) - { - this.RepetitionMode = repetitionMode; - this.ColorStops = colorStops; - } - - /// - /// Gets how the colors are repeated beyond the interval [0..1]. - /// - protected GradientRepetitionMode RepetitionMode { get; } - - /// - /// Gets the list of color stops for this gradient. - /// - protected ColorStop[] ColorStops { get; } - - /// - public abstract BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) - where TPixel : struct, IPixel; - - /// - /// Base class for gradient brush applicators - /// - internal abstract class GradientBrushApplicator : BrushApplicator - where TPixel : struct, IPixel - { - private static readonly TPixel Transparent = Color.Transparent.ToPixel(); - - private readonly ColorStop[] colorStops; - - private readonly GradientRepetitionMode repetitionMode; - - /// - /// Initializes a new instance of the class. - /// - /// The target. - /// The options. - /// An array of color stops sorted by their position. - /// Defines if and how the gradient should be repeated. - protected GradientBrushApplicator( - ImageFrame target, - GraphicsOptions options, - ColorStop[] colorStops, - GradientRepetitionMode repetitionMode) - : base(target, options) - { - this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked? - this.repetitionMode = repetitionMode; - } - - /// - /// Base implementation of the indexer for gradients - /// (follows the facade pattern, using abstract methods) - /// - /// X coordinate of the Pixel. - /// Y coordinate of the Pixel. - internal override TPixel this[int x, int y] - { - get - { - float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f); - - switch (this.repetitionMode) - { - case GradientRepetitionMode.None: - // do nothing. The following could be done, but is not necessary: - // onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient)); - break; - case GradientRepetitionMode.Repeat: - positionOnCompleteGradient = positionOnCompleteGradient % 1; - break; - case GradientRepetitionMode.Reflect: - positionOnCompleteGradient = positionOnCompleteGradient % 2; - if (positionOnCompleteGradient > 1) - { - positionOnCompleteGradient = 2 - positionOnCompleteGradient; - } - - break; - case GradientRepetitionMode.DontFill: - if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) - { - return Transparent; - } - - break; - default: - throw new ArgumentOutOfRangeException(); - } - - (ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient); - - if (from.Color.Equals(to.Color)) - { - return from.Color.ToPixel(); - } - else - { - var fromAsVector = from.Color.ToVector4(); - var toAsVector = to.Color.ToVector4(); - float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio); - - // TODO: this should be changeble for different gradienting functions - Vector4 result = PorterDuffFunctions.NormalSrcOver( - fromAsVector, - toAsVector, - onLocalGradient); - - TPixel resultColor = default; - resultColor.FromVector4(result); - return resultColor; - } - } - } - - /// - /// calculates the position on the gradient for a given point. - /// This method is abstract as it's content depends on the shape of the gradient. - /// - /// The x coordinate of the point - /// The y coordinate of the point - /// - /// The position the given point has on the gradient. - /// The position is not bound to the [0..1] interval. - /// Values outside of that interval may be treated differently, - /// e.g. for the enum. - /// - protected abstract float PositionOnGradient(float x, float y); - - private (ColorStop from, ColorStop to) GetGradientSegment( - float positionOnCompleteGradient) - { - ColorStop localGradientFrom = this.colorStops[0]; - ColorStop localGradientTo = default; - - // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) - foreach (ColorStop colorStop in this.colorStops) - { - localGradientTo = colorStop; - - if (colorStop.Ratio > positionOnCompleteGradient) - { - // we're done here, so break it! - break; - } - - localGradientFrom = localGradientTo; - } - - return (localGradientFrom, localGradientTo); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs b/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs deleted file mode 100644 index 6aed8a030c..0000000000 --- a/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Modes to repeat a gradient. - /// - public enum GradientRepetitionMode - { - /// - /// don't repeat, keep the color of start and end beyond those points stable. - /// - None, - - /// - /// Repeat the gradient. - /// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|... - /// - Repeat, - - /// - /// Reflect the gradient. - /// Similar to , but each other repetition uses inverse order of s. - /// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White... - /// - Reflect, - - /// - /// With DontFill a gradient does not touch any pixel beyond it's borders. - /// For the this is beyond the orthogonal through start and end, - /// TODO For the cref="PolygonalGradientBrush" it's outside the polygon, - /// For and it's beyond 1.0. - /// - DontFill - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/IBrush.cs b/src/ImageSharp.Drawing/Processing/IBrush.cs deleted file mode 100644 index 0cd2e20fda..0000000000 --- a/src/ImageSharp.Drawing/Processing/IBrush.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Brush represents a logical configuration of a brush which can be used to source pixel colors - /// - /// - /// A brush is a simple class that will return an that will perform the - /// logic for retrieving pixel values for specific locations. - /// - public interface IBrush - { - /// - /// Creates the applicator for this brush. - /// - /// The pixel type. - /// The source image. - /// The region the brush will be applied to. - /// The graphic options - /// - /// The brush applicator for this brush - /// - /// - /// The when being applied to things like shapes would usually be the - /// bounding box of the shape not necessarily the bounds of the whole image - /// - BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) - where TPixel : struct, IPixel; - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/IPen.cs b/src/ImageSharp.Drawing/Processing/IPen.cs deleted file mode 100644 index 0efcfc108c..0000000000 --- a/src/ImageSharp.Drawing/Processing/IPen.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Interface representing the pattern and size of the stroke to apply with a Pen. - /// - public interface IPen - { - /// - /// Gets the stroke fill. - /// - IBrush StrokeFill { get; } - - /// - /// Gets the width to apply to the stroke - /// - float StrokeWidth { get; } - - /// - /// Gets the stoke pattern. - /// - ReadOnlySpan StrokePattern { get; } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/ImageBrush.cs b/src/ImageSharp.Drawing/Processing/ImageBrush.cs deleted file mode 100644 index 8485ddfd09..0000000000 --- a/src/ImageSharp.Drawing/Processing/ImageBrush.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides an implementation of an image brush for painting images within areas. - /// - public class ImageBrush : IBrush - { - /// - /// The image to paint. - /// - private readonly Image image; - - /// - /// Initializes a new instance of the class. - /// - /// The image. - public ImageBrush(Image image) - { - this.image = image; - } - - /// - public BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) - where TPixel : struct, IPixel - { - if (this.image is Image specificImage) - { - return new ImageBrushApplicator(source, specificImage, region, options, false); - } - - specificImage = this.image.CloneAs(); - - return new ImageBrushApplicator(source, specificImage, region, options, true); - } - - /// - /// The image brush applicator. - /// - private class ImageBrushApplicator : BrushApplicator - where TPixel : struct, IPixel - { - private ImageFrame sourceFrame; - - private Image sourceImage; - - private readonly bool shouldDisposeImage; - - /// - /// The y-length. - /// - private readonly int yLength; - - /// - /// The x-length. - /// - private readonly int xLength; - - /// - /// The Y offset. - /// - private readonly int offsetY; - - /// - /// The X offset. - /// - private readonly int offsetX; - - /// - /// Initializes a new instance of the class. - /// - /// The target image. - /// The image. - /// The region. - /// The options - /// Whether to dispose the image on disposal of the applicator. - public ImageBrushApplicator( - ImageFrame target, - Image image, - RectangleF region, - GraphicsOptions options, - bool shouldDisposeImage) - : base(target, options) - { - this.sourceImage = image; - this.sourceFrame = image.Frames.RootFrame; - this.shouldDisposeImage = shouldDisposeImage; - this.xLength = image.Width; - this.yLength = image.Height; - this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); - this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0); - } - - /// - /// Gets the color for a single pixel. - /// - /// The x. - /// The y. - /// - /// The color - /// - internal override TPixel this[int x, int y] - { - get - { - int srcX = (x - this.offsetX) % this.xLength; - int srcY = (y - this.offsetY) % this.yLength; - return this.sourceFrame[srcX, srcY]; - } - } - - /// - public override void Dispose() - { - if (this.shouldDisposeImage) - { - this.sourceImage?.Dispose(); - this.sourceImage = null; - this.sourceFrame = null; - } - } - - /// - internal override void Apply(Span scanline, int x, int y) - { - // Create a span for colors - using (IMemoryOwner amountBuffer = this.Target.MemoryAllocator.Allocate(scanline.Length)) - using (IMemoryOwner overlay = this.Target.MemoryAllocator.Allocate(scanline.Length)) - { - Span amountSpan = amountBuffer.GetSpan(); - Span overlaySpan = overlay.GetSpan(); - - int sourceY = (y - this.offsetY) % this.yLength; - int offsetX = x - this.offsetX; - Span sourceRow = this.sourceFrame.GetPixelRowSpan(sourceY); - - for (int i = 0; i < scanline.Length; i++) - { - amountSpan[i] = scanline[i] * this.Options.BlendPercentage; - - int sourceX = (i + offsetX) % this.xLength; - TPixel pixel = sourceRow[sourceX]; - overlaySpan[i] = pixel; - } - - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend( - this.sourceFrame.Configuration, - destinationRow, - destinationRow, - overlaySpan, - amountSpan); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs deleted file mode 100644 index bb99eeb26a..0000000000 --- a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides an implementation of a brush for painting linear gradients within areas. - /// Supported right now: - /// - a set of colors in relative distances to each other. - /// - public sealed class LinearGradientBrush : GradientBrush - { - private readonly PointF p1; - - private readonly PointF p2; - - /// - /// Initializes a new instance of the class. - /// - /// Start point - /// End point - /// defines how colors are repeated. - /// - public LinearGradientBrush( - PointF p1, - PointF p2, - GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) - : base(repetitionMode, colorStops) - { - this.p1 = p1; - this.p2 = p2; - } - - /// - public override BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) => - new LinearGradientBrushApplicator( - source, - this.p1, - this.p2, - this.ColorStops, - this.RepetitionMode, - options); - - /// - /// The linear gradient brush applicator. - /// - private sealed class LinearGradientBrushApplicator : GradientBrushApplicator - where TPixel : struct, IPixel - { - private readonly PointF start; - - private readonly PointF end; - - /// - /// the vector along the gradient, x component - /// - private readonly float alongX; - - /// - /// the vector along the gradient, y component - /// - private readonly float alongY; - - /// - /// the vector perpendicular to the gradient, y component - /// - private readonly float acrossY; - - /// - /// the vector perpendicular to the gradient, x component - /// - private readonly float acrossX; - - /// - /// the result of ^2 + ^2 - /// - private readonly float alongsSquared; - - /// - /// the length of the defined gradient (between source and end) - /// - private readonly float length; - - /// - /// Initializes a new instance of the class. - /// - /// The source - /// start point of the gradient - /// end point of the gradient - /// tuple list of colors and their respective position between 0 and 1 on the line - /// defines how the gradient colors are repeated. - /// the graphics options - public LinearGradientBrushApplicator( - ImageFrame source, - PointF start, - PointF end, - ColorStop[] colorStops, - GradientRepetitionMode repetitionMode, - GraphicsOptions options) - : base(source, options, colorStops, repetitionMode) - { - this.start = start; - this.end = end; - - // the along vector: - this.alongX = this.end.X - this.start.X; - this.alongY = this.end.Y - this.start.Y; - - // the cross vector: - this.acrossX = this.alongY; - this.acrossY = -this.alongX; - - // some helpers: - this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); - this.length = MathF.Sqrt(this.alongsSquared); - } - - protected override float PositionOnGradient(float x, float y) - { - if (this.acrossX == 0) - { - return (x - this.start.X) / (this.end.X - this.start.X); - } - else if (this.acrossY == 0) - { - return (y - this.start.Y) / (this.end.Y - this.start.Y); - } - else - { - float deltaX = x - this.start.X; - float deltaY = y - this.start.Y; - float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared; - - // point on the line: - float x4 = x - (k * this.alongY); - float y4 = y + (k * this.alongX); - - // get distance from (x4,y4) to start - float distance = MathF.Sqrt(MathF.Pow(x4 - this.start.X, 2) + MathF.Pow(y4 - this.start.Y, 2)); - - // get and return ratio - float ratio = distance / this.length; - return ratio; - } - } - - public override void Dispose() - { - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs deleted file mode 100644 index 7315dc5a3e..0000000000 --- a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides an implementation of a brush for painting gradients between multiple color positions in 2D coordinates. - /// It works similarly with the class in System.Drawing.Drawing2D of the same name. - /// - public sealed class PathGradientBrush : IBrush - { - private readonly IList edges; - - private readonly Color centerColor; - - /// - /// Initializes a new instance of the class. - /// - /// Points that constitute a polygon that represents the gradient area. - /// Array of colors that correspond to each point in the polygon. - /// Color at the center of the gradient area to which the other colors converge. - public PathGradientBrush(PointF[] points, Color[] colors, Color centerColor) - { - if (points == null) - { - throw new ArgumentNullException(nameof(points)); - } - - if (points.Length < 3) - { - throw new ArgumentOutOfRangeException( - nameof(points), - "There must be at least 3 lines to construct a path gradient brush."); - } - - if (colors == null) - { - throw new ArgumentNullException(nameof(colors)); - } - - if (!colors.Any()) - { - throw new ArgumentOutOfRangeException( - nameof(colors), - "One or more color is needed to construct a path gradient brush."); - } - - int size = points.Length; - - var lines = new ILineSegment[size]; - - for (int i = 0; i < size; i++) - { - lines[i] = new LinearLineSegment(points[i % size], points[(i + 1) % size]); - } - - this.centerColor = centerColor; - - Color ColorAt(int index) => colors[index % colors.Length]; - - this.edges = lines.Select(s => new Path(s)) - .Select((path, i) => new Edge(path, ColorAt(i), ColorAt(i + 1))).ToList(); - } - - /// - /// Initializes a new instance of the class. - /// - /// Points that constitute a polygon that represents the gradient area. - /// Array of colors that correspond to each point in the polygon. - public PathGradientBrush(PointF[] points, Color[] colors) - : this(points, colors, CalculateCenterColor(colors)) - { - } - - /// - public BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) - where TPixel : struct, IPixel - { - return new PathGradientBrushApplicator(source, this.edges, this.centerColor, options); - } - - private static Color CalculateCenterColor(Color[] colors) - { - if (colors == null) - { - throw new ArgumentNullException(nameof(colors)); - } - - if (!colors.Any()) - { - throw new ArgumentOutOfRangeException( - nameof(colors), - "One or more color is needed to construct a path gradient brush."); - } - - return new Color(colors.Select(c => c.ToVector4()).Aggregate((p1, p2) => p1 + p2) / colors.Length); - } - - private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length(); - - private struct Intersection - { - public Intersection(PointF point, float distance) - { - this.Point = point; - this.Distance = distance; - } - - public PointF Point { get; } - - public float Distance { get; } - } - - /// - /// An edge of the polygon that represents the gradient area. - /// - private class Edge - { - private readonly Path path; - - private readonly float length; - - private readonly PointF[] buffer; - - public Edge(Path path, Color startColor, Color endColor) - { - this.path = path; - - Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray(); - - this.Start = points.First(); - this.StartColor = startColor.ToVector4(); - - this.End = points.Last(); - this.EndColor = endColor.ToVector4(); - - this.length = DistanceBetween(this.End, this.Start); - this.buffer = new PointF[this.path.MaxIntersections]; - } - - public PointF Start { get; } - - public Vector4 StartColor { get; } - - public PointF End { get; } - - public Vector4 EndColor { get; } - - public Intersection? FindIntersection(PointF start, PointF end) - { - int intersections = this.path.FindIntersections(start, end, this.buffer); - - if (intersections == 0) - { - return null; - } - - return this.buffer.Take(intersections) - .Select(p => new Intersection(point: p, distance: ((Vector2)(p - start)).LengthSquared())) - .Aggregate((min, current) => min.Distance > current.Distance ? current : min); - } - - public Vector4 ColorAt(float distance) - { - float ratio = this.length > 0 ? distance / this.length : 0; - - return Vector4.Lerp(this.StartColor, this.EndColor, ratio); - } - - public Vector4 ColorAt(PointF point) => this.ColorAt(DistanceBetween(point, this.Start)); - } - - /// - /// The path gradient brush applicator. - /// - private class PathGradientBrushApplicator : BrushApplicator - where TPixel : struct, IPixel - { - private readonly PointF center; - - private readonly Vector4 centerColor; - - private readonly float maxDistance; - - private readonly IList edges; - - /// - /// Initializes a new instance of the class. - /// - /// The source image. - /// Edges of the polygon. - /// Color at the center of the gradient area to which the other colors converge. - /// The options. - public PathGradientBrushApplicator( - ImageFrame source, - IList edges, - Color centerColor, - GraphicsOptions options) - : base(source, options) - { - this.edges = edges; - - PointF[] points = edges.Select(s => s.Start).ToArray(); - - this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count; - this.centerColor = centerColor.ToVector4(); - - this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max(); - } - - /// - internal override TPixel this[int x, int y] - { - get - { - var point = new PointF(x, y); - - if (point == this.center) - { - return new Color(this.centerColor).ToPixel(); - } - - Vector2 direction = Vector2.Normalize(point - this.center); - - PointF end = point + (PointF)(direction * this.maxDistance); - - (Edge edge, Intersection? info) = this.FindIntersection(point, end); - - if (!info.HasValue) - { - return Color.Transparent.ToPixel(); - } - - PointF intersection = info.Value.Point; - - Vector4 edgeColor = edge.ColorAt(intersection); - - float length = DistanceBetween(intersection, this.center); - float ratio = length > 0 ? DistanceBetween(intersection, point) / length : 0; - - Vector4 color = Vector4.Lerp(edgeColor, this.centerColor, ratio); - - return new Color(color).ToPixel(); - } - } - - private (Edge edge, Intersection? info) FindIntersection(PointF start, PointF end) - { - (Edge edge, Intersection? info) closest = default; - - foreach (Edge edge in this.edges) - { - Intersection? intersection = edge.FindIntersection(start, end); - - if (!intersection.HasValue) - { - continue; - } - - if (closest.info == null || closest.info.Value.Distance > intersection.Value.Distance) - { - closest = (edge, intersection); - } - } - - return closest; - } - - /// - public override void Dispose() - { - } - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush.cs b/src/ImageSharp.Drawing/Processing/PatternBrush.cs deleted file mode 100644 index 1999af8a39..0000000000 --- a/src/ImageSharp.Drawing/Processing/PatternBrush.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides an implementation of a pattern brush for painting patterns. - /// - /// - /// The patterns that are used to create a custom pattern brush are made up of a repeating matrix of flags, - /// where each flag denotes whether to draw the foreground color or the background color. - /// so to create a new bool[,] with your flags - /// - /// For example if you wanted to create a diagonal line that repeat every 4 pixels you would use a pattern like so - /// 1000 - /// 0100 - /// 0010 - /// 0001 - /// - /// - /// or you want a horizontal stripe which is 3 pixels apart you would use a pattern like - /// 1 - /// 0 - /// 0 - /// - /// - public class PatternBrush : IBrush - { - /// - /// The pattern. - /// - private readonly DenseMatrix pattern; - private readonly DenseMatrix patternVector; - - /// - /// Initializes a new instance of the class. - /// - /// Color of the fore. - /// Color of the back. - /// The pattern. - public PatternBrush(Color foreColor, Color backColor, bool[,] pattern) - : this(foreColor, backColor, new DenseMatrix(pattern)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Color of the fore. - /// Color of the back. - /// The pattern. - internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix pattern) - { - var foreColorVector = foreColor.ToVector4(); - var backColorVector = backColor.ToVector4(); - this.pattern = new DenseMatrix(pattern.Columns, pattern.Rows); - this.patternVector = new DenseMatrix(pattern.Columns, pattern.Rows); - for (int i = 0; i < pattern.Data.Length; i++) - { - if (pattern.Data[i]) - { - this.pattern.Data[i] = foreColor; - this.patternVector.Data[i] = foreColorVector; - } - else - { - this.pattern.Data[i] = backColor; - this.patternVector.Data[i] = backColorVector; - } - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The brush. - internal PatternBrush(PatternBrush brush) - { - this.pattern = brush.pattern; - this.patternVector = brush.patternVector; - } - - /// - public BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) - where TPixel : struct, IPixel => - new PatternBrushApplicator( - source, - this.pattern.ToPixelMatrix(source.Configuration), - options); - - /// - /// The pattern brush applicator. - /// - private class PatternBrushApplicator : BrushApplicator - where TPixel : struct, IPixel - { - /// - /// The pattern. - /// - private readonly DenseMatrix pattern; - - /// - /// Initializes a new instance of the class. - /// - /// The source image. - /// The pattern. - /// The options - public PatternBrushApplicator(ImageFrame source, in DenseMatrix pattern, GraphicsOptions options) - : base(source, options) - { - this.pattern = pattern; - } - - /// - /// Gets the color for a single pixel. - /// # - /// The x. - /// The y. - /// - /// The Color. - /// - internal override TPixel this[int x, int y] - { - get - { - x = x % this.pattern.Columns; - y = y % this.pattern.Rows; - - // 2d array index at row/column - return this.pattern[y, x]; - } - } - - /// - public override void Dispose() - { - // noop - } - - /// - internal override void Apply(Span scanline, int x, int y) - { - int patternY = y % this.pattern.Rows; - MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; - - using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) - using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) - { - Span amountSpan = amountBuffer.GetSpan(); - Span overlaySpan = overlay.GetSpan(); - - for (int i = 0; i < scanline.Length; i++) - { - amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); - - int patternX = (x + i) % this.pattern.Columns; - overlaySpan[i] = this.pattern[patternY, patternX]; - } - - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend( - this.Target.Configuration, - destinationRow, - destinationRow, - overlaySpan, - amountSpan); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Pen.cs b/src/ImageSharp.Drawing/Processing/Pen.cs deleted file mode 100644 index ebad687d5a..0000000000 --- a/src/ImageSharp.Drawing/Processing/Pen.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides a pen that can apply a pattern to a line with a set brush and thickness - /// - /// - /// The pattern will be in to the form of new float[]{ 1f, 2f, 0.5f} this will be - /// converted into a pattern that is 3.5 times longer that the width with 3 sections - /// section 1 will be width long (making a square) and will be filled by the brush - /// section 2 will be width * 2 long and will be empty - /// section 3 will be width/2 long and will be filled - /// the the pattern will immediately repeat without gap. - /// - public class Pen : IPen - { - private readonly float[] pattern; - - /// - /// Initializes a new instance of the class. - /// - /// The color. - /// The width. - /// The pattern. - public Pen(Color color, float width, float[] pattern) - : this(new SolidBrush(color), width, pattern) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The brush. - /// The width. - /// The pattern. - public Pen(IBrush brush, float width, float[] pattern) - { - this.StrokeFill = brush; - this.StrokeWidth = width; - this.pattern = pattern; - } - - /// - /// Initializes a new instance of the class. - /// - /// The color. - /// The width. - public Pen(Color color, float width) - : this(new SolidBrush(color), width) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The brush. - /// The width. - public Pen(IBrush brush, float width) - : this(brush, width, Pens.EmptyPattern) - { - } - - /// - public IBrush StrokeFill { get; } - - /// - public float StrokeWidth { get; } - - /// - public ReadOnlySpan StrokePattern => this.pattern; - } -} diff --git a/src/ImageSharp.Drawing/Processing/Pens.cs b/src/ImageSharp.Drawing/Processing/Pens.cs deleted file mode 100644 index e60b5b6c7c..0000000000 --- a/src/ImageSharp.Drawing/Processing/Pens.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Contains a collection of common Pen styles - /// - public static class Pens - { - private static readonly float[] DashDotPattern = { 3f, 1f, 1f, 1f }; - private static readonly float[] DashDotDotPattern = { 3f, 1f, 1f, 1f, 1f, 1f }; - private static readonly float[] DottedPattern = { 1f, 1f }; - private static readonly float[] DashedPattern = { 3f, 1f }; - internal static readonly float[] EmptyPattern = new float[0]; - - /// - /// Create a solid pen with out any drawing patterns - /// - /// The color. - /// The width. - /// The Pen - public static Pen Solid(Color color, float width) => new Pen(color, width); - - /// - /// Create a solid pen with out any drawing patterns - /// - /// The brush. - /// The width. - /// The Pen - public static Pen Solid(IBrush brush, float width) => new Pen(brush, width); - - /// - /// Create a pen with a 'Dash' drawing patterns - /// - /// The color. - /// The width. - /// The Pen - public static Pen Dash(Color color, float width) => new Pen(color, width, DashedPattern); - - /// - /// Create a pen with a 'Dash' drawing patterns - /// - /// The brush. - /// The width. - /// The Pen - public static Pen Dash(IBrush brush, float width) => new Pen(brush, width, DashedPattern); - - /// - /// Create a pen with a 'Dot' drawing patterns - /// - /// The color. - /// The width. - /// The Pen - public static Pen Dot(Color color, float width) => new Pen(color, width, DottedPattern); - - /// - /// Create a pen with a 'Dot' drawing patterns - /// - /// The brush. - /// The width. - /// The Pen - public static Pen Dot(IBrush brush, float width) => new Pen(brush, width, DottedPattern); - - /// - /// Create a pen with a 'Dash Dot' drawing patterns - /// - /// The color. - /// The width. - /// The Pen - public static Pen DashDot(Color color, float width) => new Pen(color, width, DashDotPattern); - - /// - /// Create a pen with a 'Dash Dot' drawing patterns - /// - /// The brush. - /// The width. - /// The Pen - public static Pen DashDot(IBrush brush, float width) => new Pen(brush, width, DashDotPattern); - - /// - /// Create a pen with a 'Dash Dot Dot' drawing patterns - /// - /// The color. - /// The width. - /// The Pen - public static Pen DashDotDot(Color color, float width) => new Pen(color, width, DashDotDotPattern); - - /// - /// Create a pen with a 'Dash Dot Dot' drawing patterns - /// - /// The brush. - /// The width. - /// The Pen - public static Pen DashDotDot(IBrush brush, float width) => new Pen(brush, width, DashDotDotPattern); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs deleted file mode 100644 index e217fd9a6c..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Combines two images together by blending the pixels. - /// - public class DrawImageProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The image to blend. - /// The location to draw the blended image. - /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. - public DrawImageProcessor( - Image image, - Point location, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - { - this.Image = image; - this.Location = location; - this.ColorBlendingMode = colorBlendingMode; - this.AlphaCompositionMode = alphaCompositionMode; - this.Opacity = opacity; - } - - /// - /// Gets the image to blend. - /// - public Image Image { get; } - - /// - /// Gets the location to draw the blended image. - /// - public Point Location { get; } - - /// - /// Gets the blending mode to use when drawing the image. - /// - public PixelColorBlendingMode ColorBlendingMode { get; } - - /// - /// Gets the Alpha blending mode to use when drawing the image. - /// - public PixelAlphaCompositionMode AlphaCompositionMode { get; } - - /// - /// Gets the opacity of the image to blend. - /// - public float Opacity { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixelBg : struct, IPixel - { - var visitor = new ProcessorFactoryVisitor(this, source, sourceRectangle); - this.Image.AcceptVisitor(visitor); - return visitor.Result; - } - - private class ProcessorFactoryVisitor : IImageVisitor - where TPixelBg : struct, IPixel - { - private readonly DrawImageProcessor definition; - private readonly Image source; - private readonly Rectangle sourceRectangle; - - public ProcessorFactoryVisitor(DrawImageProcessor definition, Image source, Rectangle sourceRectangle) - { - this.definition = definition; - this.source = source; - this.sourceRectangle = sourceRectangle; - } - - public IImageProcessor Result { get; private set; } - - public void Visit(Image image) - where TPixelFg : struct, IPixel - { - this.Result = new DrawImageProcessor( - image, - this.source, - this.sourceRectangle, - this.definition.Location, - this.definition.ColorBlendingMode, - this.definition.AlphaCompositionMode, - this.definition.Opacity); - } - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs deleted file mode 100644 index 76082136c7..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Combines two images together by blending the pixels. - /// - /// The pixel format of destination image. - /// The pixel format of source image. - internal class DrawImageProcessor : ImageProcessor - where TPixelBg : struct, IPixel - where TPixelFg : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The foreground to blend with the currently processing image. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The location to draw the blended image. - /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. Must be between 0 and 1. - public DrawImageProcessor( - Image image, - Image source, - Rectangle sourceRectangle, - Point location, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - : base(source, sourceRectangle) - { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - - this.Image = image; - this.Opacity = opacity; - this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); - this.Location = location; - } - - /// - /// Gets the image to blend - /// - public Image Image { get; } - - /// - /// Gets the opacity of the image to blend - /// - public float Opacity { get; } - - /// - /// Gets the pixel blender - /// - public PixelBlender Blender { get; } - - /// - /// Gets the location to draw the blended image - /// - public Point Location { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - - Image targetImage = this.Image; - PixelBlender blender = this.Blender; - int locationY = this.Location.Y; - - // Align start/end positions. - Rectangle bounds = targetImage.Bounds(); - - int minX = Math.Max(this.Location.X, sourceRectangle.X); - int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); - int targetX = minX - this.Location.X; - - int minY = Math.Max(this.Location.Y, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - - int width = maxX - minX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - // Not a valid operation because rectangle does not overlap with this image. - if (workingRect.Width <= 0 || workingRect.Height <= 0) - { - throw new ImageProcessingException( - "Cannot draw image because the source image does not overlap the target image."); - } - - ParallelHelper.IterateRows( - workingRect, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span background = source.GetPixelRowSpan(y).Slice(minX, width); - Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); - blender.Blend(configuration, background, background, foreground, this.Opacity); - } - }); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs deleted file mode 100644 index 1d3cf35576..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Defines a processor to fill an with the given - /// using blending defined by the given . - /// - public class FillProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The brush to use for filling. - /// The defining how to blend the brush pixels over the image pixels. - public FillProcessor(IBrush brush, GraphicsOptions options) - { - this.Brush = brush; - this.Options = options; - } - - /// - /// Gets the used for filling the destination image. - /// - public IBrush Brush { get; } - - /// - /// Gets the defining how to blend the brush pixels over the image pixels. - /// - public GraphicsOptions Options { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new FillProcessor(this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs deleted file mode 100644 index a7c22f6d7b..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Using the brush as a source of pixels colors blends the brush color with source. - /// - /// The pixel format. - internal class FillProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly FillProcessor definition; - - public FillProcessor(FillProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - int width = maxX - minX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - IBrush brush = this.definition.Brush; - GraphicsOptions options = this.definition.Options; - - // If there's no reason for blending, then avoid it. - if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) - { - ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); - - TPixel colorPixel = solidBrush.Color.ToPixel(); - - ParallelHelper.IterateRows( - workingRect, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - source.GetPixelRowSpan(y).Slice(minX, width).Fill(colorPixel); - } - }); - } - else - { - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) - using (BrushApplicator applicator = brush.CreateApplicator( - source, - sourceRectangle, - options)) - { - amount.GetSpan().Fill(1f); - - ParallelHelper.IterateRows( - workingRect, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - int offsetX = minX - startX; - - applicator.Apply(amount.GetSpan(), offsetX, offsetY); - } - }); - } - } - } - - private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) - { - solidBrush = this.definition.Brush as SolidBrush; - - if (solidBrush == null) - { - return false; - } - - return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs deleted file mode 100644 index 2318f3168b..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Defines a processor to fill pixels withing a given - /// with the given and blending defined by the given . - /// - public class FillRegionProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The details how to fill the region of interest. - /// The region of interest to be filled. - /// The configuration options. - public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) - { - this.Region = region; - this.Brush = brush; - this.Options = options; - } - - /// - /// Gets the used for filling the destination image. - /// - public IBrush Brush { get; } - - /// - /// Gets the region that this processor applies to. - /// - public Region Region { get; } - - /// - /// Gets the defining how to blend the brush pixels over the image pixels. - /// - public GraphicsOptions Options { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new FillRegionProcessor(this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs deleted file mode 100644 index 45d5015ae0..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Utils; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Using a brush and a shape fills shape with contents of brush the - /// - /// The type of the color. - /// - internal class FillRegionProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly FillRegionProcessor definition; - - public FillRegionProcessor(FillRegionProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Configuration configuration = this.Configuration; - GraphicsOptions options = this.definition.Options; - IBrush brush = this.definition.Brush; - Region region = this.definition.Region; - Rectangle rect = region.Bounds; - - // Align start/end positions. - int minX = Math.Max(0, rect.Left); - int maxX = Math.Min(source.Width, rect.Right); - int minY = Math.Max(0, rect.Top); - int maxY = Math.Min(source.Height, rect.Bottom); - if (minX >= maxX) - { - return; // no effect inside image; - } - - if (minY >= maxY) - { - return; // no effect inside image; - } - - int maxIntersections = region.MaxIntersections; - float subpixelCount = 4; - - // we need to offset the pixel grid to account for when we outline a path. - // basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5] - // and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the# - // region to align with the pixel grid. - float offset = 0.5f; - if (options.Antialias) - { - offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset. - subpixelCount = options.AntialiasSubpixelDepth; - if (subpixelCount < 4) - { - subpixelCount = 4; - } - } - - using (BrushApplicator applicator = brush.CreateApplicator(source, rect, options)) - { - int scanlineWidth = maxX - minX; - using (IMemoryOwner bBuffer = source.MemoryAllocator.Allocate(maxIntersections)) - using (IMemoryOwner bScanline = source.MemoryAllocator.Allocate(scanlineWidth)) - { - bool scanlineDirty = true; - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - - Span buffer = bBuffer.GetSpan(); - Span scanline = bScanline.GetSpan(); - - bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush); - TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel() : default; - - for (int y = minY; y < maxY; y++) - { - if (scanlineDirty) - { - scanline.Clear(); - scanlineDirty = false; - } - - float yPlusOne = y + 1; - for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction) - { - int pointsFound = region.Scan(subPixel + offset, buffer, configuration); - if (pointsFound == 0) - { - // nothing on this line, skip - continue; - } - - QuickSort.Sort(buffer.Slice(0, pointsFound)); - - for (int point = 0; point < pointsFound && point < buffer.Length - 1; point += 2) - { - // points will be paired up - float scanStart = buffer[point] - minX; - float scanEnd = buffer[point + 1] - minX; - int startX = (int)MathF.Floor(scanStart + offset); - int endX = (int)MathF.Floor(scanEnd + offset); - - if (startX >= 0 && startX < scanline.Length) - { - for (float x = scanStart; x < startX + 1; x += subpixelFraction) - { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; - } - } - - if (endX >= 0 && endX < scanline.Length) - { - for (float x = endX; x < scanEnd; x += subpixelFraction) - { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; - } - } - - int nextX = startX + 1; - endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - nextX = Math.Max(nextX, 0); - for (int x = nextX; x < endX; x++) - { - scanline[x] += subpixelFraction; - scanlineDirty = true; - } - } - } - - if (scanlineDirty) - { - if (!options.Antialias) - { - bool hasOnes = false; - bool hasZeros = false; - for (int x = 0; x < scanlineWidth; x++) - { - if (scanline[x] >= 0.5) - { - scanline[x] = 1; - hasOnes = true; - } - else - { - scanline[x] = 0; - hasZeros = true; - } - } - - if (isSolidBrushWithoutBlending && hasOnes != hasZeros) - { - if (hasOnes) - { - source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor); - } - - continue; - } - } - - applicator.Apply(scanline, minX, y); - } - } - } - } - } - - private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) - { - solidBrush = this.definition.Brush as SolidBrush; - - if (solidBrush == null) - { - return false; - } - - return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs deleted file mode 100644 index 775cf55abf..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.Fonts; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Text -{ - /// - /// Defines a processor to draw text on an . - /// - public class DrawTextProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The options - /// The text we want to render - /// The font we want to render with - /// The brush to source pixel colors from. - /// The pen to outline text with. - /// The location on the image to start drawing the text from. - public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush brush, IPen pen, PointF location) - { - Guard.NotNull(text, nameof(text)); - Guard.NotNull(font, nameof(font)); - - if (brush is null && pen is null) - { - throw new ArgumentNullException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null"); - } - - this.Options = options; - this.Text = text; - this.Font = font; - this.Location = location; - this.Brush = brush; - this.Pen = pen; - } - - /// - /// Gets the brush used to fill the glyphs. - /// - public IBrush Brush { get; } - - /// - /// Gets the defining blending modes and text-specific drawing settings. - /// - public TextGraphicsOptions Options { get; } - - /// - /// Gets the text to draw. - /// - public string Text { get; } - - /// - /// Gets the pen used for outlining the text, if Null then we will not outline - /// - public IPen Pen { get; } - - /// - /// Gets the font used to render the text. - /// - public Font Font { get; } - - /// - /// Gets the location to draw the text at. - /// - public PointF Location { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new DrawTextProcessor(this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs deleted file mode 100644 index ea042635dd..0000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Collections.Generic; - -using SixLabors.Fonts; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Utils; -using SixLabors.Memory; -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Processing.Processors.Text -{ - /// - /// Using the brush as a source of pixels colors blends the brush color with source. - /// - /// The pixel format. - internal class DrawTextProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private CachingGlyphRenderer textRenderer; - - private readonly DrawTextProcessor definition; - - public DrawTextProcessor(DrawTextProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } - - private TextGraphicsOptions Options => this.definition.Options; - - private Font Font => this.definition.Font; - - private PointF Location => this.definition.Location; - - private string Text => this.definition.Text; - - private IPen Pen => this.definition.Pen; - - private IBrush Brush => this.definition.Brush; - - protected override void BeforeImageApply() - { - base.BeforeImageApply(); - - // do everything at the image level as we are delegating the processing down to other processors - var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location) - { - ApplyKerning = this.Options.ApplyKerning, - TabWidth = this.Options.TabWidth, - WrappingWidth = this.Options.WrapTextWidth, - HorizontalAlignment = this.Options.HorizontalAlignment, - VerticalAlignment = this.Options.VerticalAlignment - }; - - this.textRenderer = new CachingGlyphRenderer(this.Source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null); - this.textRenderer.Options = (GraphicsOptions)this.Options; - var renderer = new TextRenderer(this.textRenderer); - renderer.RenderText(this.Text, style); - } - - protected override void AfterImageApply() - { - base.AfterImageApply(); - this.textRenderer?.Dispose(); - this.textRenderer = null; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - // this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome - Draw(this.textRenderer.FillOperations, this.Brush); - Draw(this.textRenderer.OutlineOperations, this.Pen?.StrokeFill); - - void Draw(List operations, IBrush brush) - { - if (operations?.Count > 0) - { - using (BrushApplicator app = brush.CreateApplicator(source, this.SourceRectangle, this.textRenderer.Options)) - { - foreach (DrawingOperation operation in operations) - { - Buffer2D buffer = operation.Map; - int startY = operation.Location.Y; - int startX = operation.Location.X; - int offsetSpan = 0; - if (startX < 0) - { - offsetSpan = -startX; - startX = 0; - } - - if (startX >= source.Width) - { - continue; - } - - int firstRow = 0; - if (startY < 0) - { - firstRow = -startY; - } - - int maxHeight = source.Height - startY; - int end = Math.Min(operation.Map.Height, maxHeight); - - for (int row = firstRow; row < end; row++) - { - int y = startY + row; - Span span = buffer.GetRowSpan(row).Slice(offsetSpan); - app.Apply(span, startX, y); - } - } - } - } - } - } - - private struct DrawingOperation - { - public Buffer2D Map { get; set; } - - public Point Location { get; set; } - } - - private class CachingGlyphRenderer : IGlyphRenderer, IDisposable - { - // just enough accuracy to allow for 1/8 pixel differences which - // later are accumulated while rendering, but do not grow into full pixel offsets - // The value 8 is benchmarked to: - // - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant) - // - Cache hit ratio above 60% - private const float AccuracyMultiple = 8; - - private readonly PathBuilder builder; - - private Point currentRenderPosition; - private (GlyphRendererParameters glyph, PointF subPixelOffset) currentGlyphRenderParams; - private readonly int offset; - private PointF currentPoint; - - private readonly Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData> - glyphData = new Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData>(); - - private readonly bool renderOutline; - private readonly bool renderFill; - private bool rasterizationRequired; - - public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill) - { - this.MemoryAllocator = memoryAllocator; - this.currentRenderPosition = default; - this.Pen = pen; - this.renderFill = renderFill; - this.renderOutline = pen != null; - this.offset = 2; - if (this.renderFill) - { - this.FillOperations = new List(size); - } - - if (this.renderOutline) - { - this.offset = (int)MathF.Ceiling((pen.StrokeWidth * 2) + 2); - this.OutlineOperations = new List(size); - } - - this.builder = new PathBuilder(); - } - - public List FillOperations { get; } - - public List OutlineOperations { get; } - - public MemoryAllocator MemoryAllocator { get; internal set; } - - public IPen Pen { get; internal set; } - - public GraphicsOptions Options { get; internal set; } - - public void BeginFigure() - { - this.builder.StartFigure(); - } - - public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters parameters) - { - this.currentRenderPosition = Point.Truncate(bounds.Location); - PointF subPixelOffset = bounds.Location - this.currentRenderPosition; - - subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple; - subPixelOffset.Y = MathF.Round(subPixelOffset.Y * AccuracyMultiple) / AccuracyMultiple; - - // we have offset our rendering origin a little bit down to prevent edge cropping, move the draw origin up to compensate - this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset); - this.currentGlyphRenderParams = (parameters, subPixelOffset); - - if (this.glyphData.ContainsKey(this.currentGlyphRenderParams)) - { - // we have already drawn the glyph vectors skip trying again - this.rasterizationRequired = false; - return false; - } - - // we check to see if we have a render cache and if we do then we render else - this.builder.Clear(); - - // ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offset it back - this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset)); - - this.rasterizationRequired = true; - return true; - } - - public void BeginText(RectangleF bounds) - { - // not concerned about this one - this.OutlineOperations?.Clear(); - this.FillOperations?.Clear(); - } - - public void CubicBezierTo(PointF secondControlPoint, PointF thirdControlPoint, PointF point) - { - this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); - this.currentPoint = point; - } - - public void Dispose() - { - foreach (KeyValuePair<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData> kv in this.glyphData) - { - kv.Value.Dispose(); - } - - this.glyphData.Clear(); - } - - public void EndFigure() - { - this.builder.CloseFigure(); - } - - public void EndGlyph() - { - GlyphRenderData renderData = default; - - // has the glyph been rendered already? - if (this.rasterizationRequired) - { - IPath path = this.builder.Build(); - - if (this.renderFill) - { - renderData.FillMap = this.Render(path); - } - - if (this.renderOutline) - { - if (this.Pen.StrokePattern.Length == 0) - { - path = path.GenerateOutline(this.Pen.StrokeWidth); - } - else - { - path = path.GenerateOutline(this.Pen.StrokeWidth, this.Pen.StrokePattern); - } - - renderData.OutlineMap = this.Render(path); - } - - this.glyphData[this.currentGlyphRenderParams] = renderData; - } - else - { - renderData = this.glyphData[this.currentGlyphRenderParams]; - } - - if (this.renderFill) - { - this.FillOperations.Add(new DrawingOperation - { - Location = this.currentRenderPosition, - Map = renderData.FillMap - }); - } - - if (this.renderOutline) - { - this.OutlineOperations.Add(new DrawingOperation - { - Location = this.currentRenderPosition, - Map = renderData.OutlineMap - }); - } - } - - private Buffer2D Render(IPath path) - { - Size size = Rectangle.Ceiling(path.Bounds).Size; - size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2)); - - float subpixelCount = 4; - float offset = 0.5f; - if (this.Options.Antialias) - { - offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset. - subpixelCount = this.Options.AntialiasSubpixelDepth; - if (subpixelCount < 4) - { - subpixelCount = 4; - } - } - - // take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it. - Buffer2D fullBuffer = this.MemoryAllocator.Allocate2D(size.Width + 1, size.Height + 1, AllocationOptions.Clean); - - using (IMemoryOwner bufferBacking = this.MemoryAllocator.Allocate(path.MaxIntersections)) - using (IMemoryOwner rowIntersectionBuffer = this.MemoryAllocator.Allocate(size.Width)) - { - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - - for (int y = 0; y <= size.Height; y++) - { - Span scanline = fullBuffer.GetRowSpan(y); - bool scanlineDirty = false; - float yPlusOne = y + 1; - - for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction) - { - var start = new PointF(path.Bounds.Left - 1, subPixel); - var end = new PointF(path.Bounds.Right + 1, subPixel); - Span intersectionSpan = rowIntersectionBuffer.GetSpan(); - Span buffer = bufferBacking.GetSpan(); - int pointsFound = path.FindIntersections(start, end, intersectionSpan); - - if (pointsFound == 0) - { - // nothing on this line skip - continue; - } - - for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++) - { - buffer[i] = intersectionSpan[i].X; - } - - QuickSort.Sort(buffer.Slice(0, pointsFound)); - - for (int point = 0; point < pointsFound; point += 2) - { - // points will be paired up - float scanStart = buffer[point]; - float scanEnd = buffer[point + 1]; - int startX = (int)MathF.Floor(scanStart + offset); - int endX = (int)MathF.Floor(scanEnd + offset); - - if (startX >= 0 && startX < scanline.Length) - { - for (float x = scanStart; x < startX + 1; x += subpixelFraction) - { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; - } - } - - if (endX >= 0 && endX < scanline.Length) - { - for (float x = endX; x < scanEnd; x += subpixelFraction) - { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; - } - } - - int nextX = startX + 1; - endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - nextX = Math.Max(nextX, 0); - for (int x = nextX; x < endX; x++) - { - scanline[x] += subpixelFraction; - scanlineDirty = true; - } - } - } - - if (scanlineDirty) - { - if (!this.Options.Antialias) - { - for (int x = 0; x < size.Width; x++) - { - if (scanline[x] >= 0.5) - { - scanline[x] = 1; - } - else - { - scanline[x] = 0; - } - } - } - } - } - } - - return fullBuffer; - } - - public void EndText() - { - } - - public void LineTo(PointF point) - { - this.builder.AddLine(this.currentPoint, point); - this.currentPoint = point; - } - - public void MoveTo(PointF point) - { - this.builder.StartFigure(); - this.currentPoint = point; - } - - public void QuadraticBezierTo(PointF secondControlPoint, PointF point) - { - this.builder.AddBezier(this.currentPoint, secondControlPoint, point); - this.currentPoint = point; - } - - private struct GlyphRenderData : IDisposable - { - public Buffer2D FillMap; - - public Buffer2D OutlineMap; - - public void Dispose() - { - this.FillMap?.Dispose(); - this.OutlineMap?.Dispose(); - } - } - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs deleted file mode 100644 index f4d2dd81f4..0000000000 --- a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// A Circular Gradient Brush, defined by center point and radius. - /// - public sealed class RadialGradientBrush : GradientBrush - { - private readonly PointF center; - - private readonly float radius; - - /// - /// The center of the circular gradient and 0 for the color stops. - /// The radius of the circular gradient and 1 for the color stops. - /// Defines how the colors in the gradient are repeated. - /// the color stops as defined in base class. - public RadialGradientBrush( - PointF center, - float radius, - GradientRepetitionMode repetitionMode, - params ColorStop[] colorStops) - : base(repetitionMode, colorStops) - { - this.center = center; - this.radius = radius; - } - - /// - public override BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) => - new RadialGradientBrushApplicator( - source, - options, - this.center, - this.radius, - this.ColorStops, - this.RepetitionMode); - - /// - private sealed class RadialGradientBrushApplicator : GradientBrushApplicator - where TPixel : struct, IPixel - { - private readonly PointF center; - - private readonly float radius; - - /// - /// Initializes a new instance of the class. - /// - /// The target image - /// The options. - /// Center point of the gradient. - /// Radius of the gradient. - /// Definition of colors. - /// How the colors are repeated beyond the first gradient. - public RadialGradientBrushApplicator( - ImageFrame target, - GraphicsOptions options, - PointF center, - float radius, - ColorStop[] colorStops, - GradientRepetitionMode repetitionMode) - : base(target, options, colorStops, repetitionMode) - { - this.center = center; - this.radius = radius; - } - - /// - public override void Dispose() - { - } - - /// - /// As this is a circular gradient, the position on the gradient is based on - /// the distance of the point to the center. - /// - /// The X coordinate of the target pixel. - /// The Y coordinate of the target pixel. - /// the position on the color gradient. - protected override float PositionOnGradient(float x, float y) - { - float distance = MathF.Sqrt(MathF.Pow(this.center.X - x, 2) + MathF.Pow(this.center.Y - y, 2)); - return distance / this.radius; - } - - internal override void Apply(Span scanline, int x, int y) - { - // TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance. - base.Apply(scanline, x, y); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs deleted file mode 100644 index fca95be327..0000000000 --- a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides an implementation of a brush that can recolor an image - /// - public class RecolorBrush : IBrush - { - /// - /// Initializes a new instance of the class. - /// - /// Color of the source. - /// Color of the target. - /// The threshold as a value between 0 and 1. - public RecolorBrush(Color sourceColor, Color targetColor, float threshold) - { - this.SourceColor = sourceColor; - this.Threshold = threshold; - this.TargetColor = targetColor; - } - - /// - /// Gets the threshold. - /// - public float Threshold { get; } - - /// - /// Gets the source color. - /// - /// - /// The color of the source. - /// - public Color SourceColor { get; } - - /// - /// Gets the target color. - /// - public Color TargetColor { get; } - - /// - public BrushApplicator CreateApplicator( - ImageFrame source, - RectangleF region, - GraphicsOptions options) - where TPixel : struct, IPixel - { - return new RecolorBrushApplicator( - source, - this.SourceColor.ToPixel(), - this.TargetColor.ToPixel(), - this.Threshold, - options); - } - - /// - /// The recolor brush applicator. - /// - private class RecolorBrushApplicator : BrushApplicator - where TPixel : struct, IPixel - { - /// - /// The source color. - /// - private readonly Vector4 sourceColor; - - /// - /// The target color. - /// - private readonly Vector4 targetColor; - - /// - /// The threshold. - /// - private readonly float threshold; - - private readonly TPixel targetColorPixel; - - /// - /// Initializes a new instance of the class. - /// - /// The source image. - /// Color of the source. - /// Color of the target. - /// The threshold . - /// The options - public RecolorBrushApplicator(ImageFrame source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) - : base(source, options) - { - this.sourceColor = sourceColor.ToVector4(); - this.targetColor = targetColor.ToVector4(); - this.targetColorPixel = targetColor; - - // Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :) - var maxColor = default(TPixel); - maxColor.FromVector4(new Vector4(float.MaxValue)); - var minColor = default(TPixel); - minColor.FromVector4(new Vector4(float.MinValue)); - this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; - } - - /// - /// Gets the color for a single pixel. - /// - /// The x. - /// The y. - /// - /// The color - /// - internal override TPixel this[int x, int y] - { - get - { - // Offset the requested pixel by the value in the rectangle (the shapes position) - TPixel result = this.Target[x, y]; - var background = result.ToVector4(); - float distance = Vector4.DistanceSquared(background, this.sourceColor); - if (distance <= this.threshold) - { - float lerpAmount = (this.threshold - distance) / this.threshold; - return this.Blender.Blend( - result, - this.targetColorPixel, - lerpAmount); - } - - return result; - } - } - - /// - public override void Dispose() - { - } - - /// - internal override void Apply(Span scanline, int x, int y) - { - MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; - - using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) - using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) - { - Span amountSpan = amountBuffer.GetSpan(); - Span overlaySpan = overlay.GetSpan(); - - for (int i = 0; i < scanline.Length; i++) - { - amountSpan[i] = scanline[i] * this.Options.BlendPercentage; - - int offsetX = x + i; - - // No doubt this one can be optimized further but I can't imagine its - // actually being used and can probably be removed/internalized for now - overlaySpan[i] = this[offsetX, y]; - } - - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend( - this.Target.Configuration, - destinationRow, - destinationRow, - overlaySpan, - amountSpan); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/SolidBrush.cs b/src/ImageSharp.Drawing/Processing/SolidBrush.cs deleted file mode 100644 index c62566f6b7..0000000000 --- a/src/ImageSharp.Drawing/Processing/SolidBrush.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Provides an implementation of a solid brush for painting solid color areas. - /// - public class SolidBrush : IBrush - { - /// - /// The color to paint. - /// - private readonly Color color; - - /// - /// Initializes a new instance of the class. - /// - /// The color. - public SolidBrush(Color color) - { - this.color = color; - } - - /// - /// Gets the color. - /// - /// - /// The color. - /// - public Color Color => this.color; - - /// - public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) - where TPixel : struct, IPixel - { - return new SolidBrushApplicator(source, this.color.ToPixel(), options); - } - - /// - /// The solid brush applicator. - /// - private class SolidBrushApplicator : BrushApplicator - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The source image. - /// The color. - /// The options - public SolidBrushApplicator(ImageFrame source, TPixel color, GraphicsOptions options) - : base(source, options) - { - this.Colors = source.MemoryAllocator.Allocate(source.Width); - this.Colors.GetSpan().Fill(color); - } - - /// - /// Gets the colors. - /// - protected IMemoryOwner Colors { get; } - - /// - /// Gets the color for a single pixel. - /// - /// The x. - /// The y. - /// - /// The color - /// - internal override TPixel this[int x, int y] => this.Colors.GetSpan()[x]; - - /// - public override void Dispose() - { - this.Colors.Dispose(); - } - - /// - internal override void Apply(Span scanline, int x, int y) - { - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x); - - // constrain the spans to each other - if (destinationRow.Length > scanline.Length) - { - destinationRow = destinationRow.Slice(0, scanline.Length); - } - else - { - scanline = scanline.Slice(0, destinationRow.Length); - } - - MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; - Configuration configuration = this.Target.Configuration; - - if (this.Options.BlendPercentage == 1f) - { - this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.GetSpan(), scanline); - } - else - { - using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) - { - Span amountSpan = amountBuffer.GetSpan(); - - for (int i = 0; i < scanline.Length; i++) - { - amountSpan[i] = scanline[i] * this.Options.BlendPercentage; - } - - this.Blender.Blend( - configuration, - destinationRow, - destinationRow, - this.Colors.GetSpan(), - amountSpan); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs deleted file mode 100644 index 6c140be72e..0000000000 --- a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Fonts; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Options for influencing the drawing functions. - /// - public struct TextGraphicsOptions - { - private const int DefaultTextDpi = 72; - - /// - /// Represents the default . - /// - public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true); - - private float? blendPercentage; - - private int? antialiasSubpixelDepth; - - private bool? antialias; - - private bool? applyKerning; - - private float? tabWidth; - - private float? dpiX; - - private float? dpiY; - - private HorizontalAlignment? horizontalAlignment; - - private VerticalAlignment? verticalAlignment; - - /// - /// Initializes a new instance of the struct. - /// - /// If set to true [enable antialiasing]. - public TextGraphicsOptions(bool enableAntialiasing) - { - this.applyKerning = true; - this.tabWidth = 4; - this.WrapTextWidth = 0; - this.horizontalAlignment = HorizontalAlignment.Left; - this.verticalAlignment = VerticalAlignment.Top; - - this.antialiasSubpixelDepth = 16; - this.ColorBlendingMode = PixelColorBlendingMode.Normal; - this.AlphaCompositionMode = PixelAlphaCompositionMode.SrcOver; - this.blendPercentage = 1; - this.antialias = enableAntialiasing; - this.dpiX = DefaultTextDpi; - this.dpiY = DefaultTextDpi; - } - - /// - /// Gets or sets a value indicating whether antialiasing should be applied. - /// - public bool Antialias { get => this.antialias ?? true; set => this.antialias = value; } - - /// - /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. - /// - public int AntialiasSubpixelDepth { get => this.antialiasSubpixelDepth ?? 16; set => this.antialiasSubpixelDepth = value; } - - /// - /// Gets or sets a value indicating the blending percentage to apply to the drawing operation - /// - public float BlendPercentage { get => (this.blendPercentage ?? 1).Clamp(0, 1); set => this.blendPercentage = value; } - - // In the future we could expose a PixelBlender directly on here - // or some forms of PixelBlender factory for each pixel type. Will need - // some API thought post V1. - - /// - /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation - /// - public PixelColorBlendingMode ColorBlendingMode { get; set; } - - /// - /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation - /// - public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } - - /// - /// Gets or sets a value indicating whether the text should be drawing with kerning enabled. - /// - public bool ApplyKerning { get => this.applyKerning ?? true; set => this.applyKerning = value; } - - /// - /// Gets or sets a value indicating the number of space widths a tab should lock to. - /// - public float TabWidth { get => this.tabWidth ?? 4; set => this.tabWidth = value; } - - /// - /// Gets or sets a value indicating if greater than zero determine the width at which text should wrap. - /// - public float WrapTextWidth { get; set; } - - /// - /// Gets or sets a value indicating the DPI to render text along the X axis. - /// - public float DpiX { get => this.dpiX ?? DefaultTextDpi; set => this.dpiX = value; } - - /// - /// Gets or sets a value indicating the DPI to render text along the Y axis. - /// - public float DpiY { get => this.dpiY ?? DefaultTextDpi; set => this.dpiY = value; } - - /// - /// Gets or sets a value indicating how to align the text relative to the rendering space. - /// If is greater than zero it will align relative to the space - /// defined by the location and width, if equals zero, and thus - /// wrapping disabled, then the alignment is relative to the drawing location. - /// - public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; } - - /// - /// Gets or sets a value indicating how to align the text relative to the rendering space. - /// - public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; } - - /// - /// Performs an implicit conversion from to . - /// - /// The options. - /// - /// The result of the conversion. - /// - public static implicit operator TextGraphicsOptions(GraphicsOptions options) - { - return new TextGraphicsOptions(options.Antialias) - { - AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, - blendPercentage = options.BlendPercentage, - ColorBlendingMode = options.ColorBlendingMode, - AlphaCompositionMode = options.AlphaCompositionMode - }; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The options. - /// - /// The result of the conversion. - /// - public static explicit operator GraphicsOptions(TextGraphicsOptions options) - { - return new GraphicsOptions(options.Antialias) - { - AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, - ColorBlendingMode = options.ColorBlendingMode, - AlphaCompositionMode = options.AlphaCompositionMode, - BlendPercentage = options.BlendPercentage - }; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Utils/QuickSort.cs b/src/ImageSharp.Drawing/Utils/QuickSort.cs deleted file mode 100644 index ca1da5505a..0000000000 --- a/src/ImageSharp.Drawing/Utils/QuickSort.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Utils -{ - /// - /// Optimized quick sort implementation for Span{float} input - /// - internal class QuickSort - { - /// - /// Sorts the elements of in ascending order - /// - /// The items to sort - public static void Sort(Span data) - { - if (data.Length < 2) - { - return; - } - - if (data.Length == 2) - { - if (data[0] > data[1]) - { - Swap(ref data[0], ref data[1]); - } - - return; - } - - Sort(ref data[0], 0, data.Length - 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Swap(ref float left, ref float right) - { - float tmp = left; - left = right; - right = tmp; - } - - private static void Sort(ref float data0, int lo, int hi) - { - if (lo < hi) - { - int p = Partition(ref data0, lo, hi); - Sort(ref data0, lo, p); - Sort(ref data0, p + 1, hi); - } - } - - private static int Partition(ref float data0, int lo, int hi) - { - float pivot = Unsafe.Add(ref data0, lo); - int i = lo - 1; - int j = hi + 1; - while (true) - { - do - { - i = i + 1; - } - while (Unsafe.Add(ref data0, i) < pivot && i < hi); - - do - { - j = j - 1; - } - while (Unsafe.Add(ref data0, j) > pivot && j > lo); - - if (i >= j) - { - return j; - } - - Swap(ref Unsafe.Add(ref data0, i), ref Unsafe.Add(ref data0, j)); - } - } - } -} diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 22e6d47e9a..a79733fec3 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Advanced { @@ -30,105 +30,53 @@ namespace SixLabors.ImageSharp.Advanced /// The source image. /// Returns the configuration. public static Configuration GetConfiguration(this Image source) - => GetConfiguration((IConfigurable)source); + => GetConfiguration((IConfigurationProvider)source); /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. + /// Gets the configuration for the image frame. /// - /// The type of the pixel. - /// The source. - /// The - public static Span GetPixelSpan(this ImageFrame source) - where TPixel : struct, IPixel - => source.GetPixelMemory().Span; + /// The source image. + /// Returns the configuration. + public static Configuration GetConfiguration(this ImageFrame source) + => GetConfiguration((IConfigurationProvider)source); /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. + /// Gets the configuration. /// - /// The type of the pixel. - /// The source. - /// The - public static Span GetPixelSpan(this Image source) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelSpan(); + /// The source image + /// Returns the bounds of the image + private static Configuration GetConfiguration(IConfigurationProvider source) + => source?.Configuration ?? Configuration.Default; /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. + /// Gets the representation of the pixels as a containing the backing pixel data of the image + /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. /// + /// The source image. /// The type of the pixel. - /// The source. - /// The row. - /// The - public static Span GetPixelRowSpan(this ImageFrame source, int rowIndex) - where TPixel : struct, IPixel - => source.PixelBuffer.GetRowSpan(rowIndex); + /// The . + /// + /// Certain Image Processors may invalidate the returned and all it's buffers, + /// therefore it's not recommended to mutate the image while holding a reference to it's . + /// + public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) + where TPixel : unmanaged, IPixel + => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); /// - /// Gets the representation of the pixels as of of contiguous memory - /// at row beginning from the the first pixel on that row. + /// Gets the representation of the pixels as a containing the backing pixel data of the image + /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. /// + /// The source image. /// The type of the pixel. - /// The source. - /// The row. - /// The - public static Span GetPixelRowSpan(this Image source, int rowIndex) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowSpan(rowIndex); - - /// - /// Returns a reference to the 0th element of the Pixel buffer, - /// allowing direct manipulation of pixel data through unsafe operations. - /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order. - /// - /// The Pixel format. - /// The source image frame - /// A pinnable reference the first root of the pixel buffer. - [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] - public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source) - where TPixel : struct, IPixel - => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source); - - /// - /// Returns a reference to the 0th element of the Pixel buffer, - /// allowing direct manipulation of pixel data through unsafe operations. - /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order. - /// - /// The Pixel format. - /// The source image - /// A pinnable reference the first root of the pixel buffer. - [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] - public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source) - where TPixel : struct, IPixel - => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer(); - - /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. - /// - /// The Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this ImageFrame source) - where TPixel : struct, IPixel - { - return source.PixelBuffer.MemorySource.Memory; - } - - /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. - /// - /// The Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this Image source) - where TPixel : struct, IPixel - { - return source.Frames.RootFrame.GetPixelMemory(); - } + /// The . + /// + /// Certain Image Processors may invalidate the returned and all it's buffers, + /// therefore it's not recommended to mutate the image while holding a reference to it's . + /// + public static IMemoryGroup GetPixelMemoryGroup(this Image source) + where TPixel : unmanaged, IPixel + => source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source)); /// /// Gets the representation of the pixels as a of contiguous memory @@ -138,9 +86,15 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) - where TPixel : struct, IPixel - => source.PixelBuffer.GetRowMemory(rowIndex); + public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.PixelBuffer.GetSafeRowMemory(rowIndex); + } /// /// Gets the representation of the pixels as of of contiguous memory @@ -150,34 +104,22 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this Image source, int rowIndex) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowMemory(rowIndex); + public static Memory GetPixelRowMemory(this Image source, int rowIndex) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex); + } /// /// Gets the assigned to 'source'. /// /// The source image. /// Returns the configuration. - internal static MemoryAllocator GetMemoryAllocator(this IConfigurable source) + internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source) => GetConfiguration(source).MemoryAllocator; - - /// - /// Gets the configuration. - /// - /// The source image - /// Returns the bounds of the image - private static Configuration GetConfiguration(IConfigurable source) - => source?.Configuration ?? Configuration.Default; - - /// - /// Returns a reference to the 0th element of the Pixel buffer. - /// Such a reference can be used for pinning but must never be dereferenced. - /// - /// The source image frame - /// A reference to the element. - private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) - where TPixel : struct, IPixel - => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan()); } } diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 1ceba5f90e..23ae62c7a1 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,11 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -19,6 +22,7 @@ namespace SixLabors.ImageSharp.Advanced /// None of the methods in this class should ever be called, the code only has to exist at compile-time to be picked up by the AoT compiler. /// (Very similar to the LinkerIncludes.cs technique used in Xamarin.Android projects.) /// + [ExcludeFromCodeCoverage] internal static class AotCompilerTools { static AotCompilerTools() @@ -40,7 +44,7 @@ namespace SixLabors.ImageSharp.Advanced /// private static void SeedEverything() { - Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -48,8 +52,10 @@ namespace SixLabors.ImageSharp.Advanced Seed(); Seed(); Seed(); - Seed(); - Seed(); + Seed(); + Seed(); + Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -73,11 +79,12 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void Seed() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // This is we actually call all the individual methods you need to seed. AotCompileOctreeQuantizer(); AotCompileWuQuantizer(); + AotCompilePaletteQuantizer(); AotCompileDithering(); AotCompilePixelOperations(); @@ -103,11 +110,12 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileOctreeQuantizer() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var test = new OctreeFrameQuantizer(new OctreeQuantizer(false))) + using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer().Options)) { - test.AotGetPalette(); + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); } } @@ -116,12 +124,26 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileWuQuantizer() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var test = new WuFrameQuantizer(Configuration.Default.MemoryAllocator, new WuQuantizer(false))) + using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer().Options)) { - test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1)); - test.AotGetPalette(); + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); + } + } + + /// + /// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS. + /// + /// The pixel format. + private static void AotCompilePaletteQuantizer() + where TPixel : unmanaged, IPixel + { + using (var test = (PaletteFrameQuantizer)new PaletteQuantizer(Array.Empty()).CreateFrameQuantizer(Configuration.Default)) + { + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); } } @@ -130,13 +152,15 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileDithering() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var test = new FloydSteinbergDiffuser(); + ErrorDither errorDither = ErrorDither.FloydSteinberg; + OrderedDither orderedDither = OrderedDither.Bayer2x2; TPixel pixel = default; using (var image = new ImageFrame(Configuration.Default, 1, 1)) { - test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0); + errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0); + orderedDither.Dither(pixel, 0, 0, 0, 0); } } @@ -147,7 +171,7 @@ namespace SixLabors.ImageSharp.Advanced /// The image encoder to seed. /// The pixel format. private static void AotCodec(IImageDecoder decoder, IImageEncoder encoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { try { @@ -171,7 +195,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompilePixelOperations() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var pixelOp = new PixelOperations(); pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); diff --git a/src/ImageSharp/Advanced/IConfigurable.cs b/src/ImageSharp/Advanced/IConfigurable.cs deleted file mode 100644 index 38fc83ae1d..0000000000 --- a/src/ImageSharp/Advanced/IConfigurable.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Advanced -{ - /// - /// Encapsulates the properties for configuration. - /// - internal interface IConfigurable - { - /// - /// Gets the configuration. - /// - Configuration Configuration { get; } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Advanced/IConfigurationProvider.cs b/src/ImageSharp/Advanced/IConfigurationProvider.cs new file mode 100644 index 0000000000..d3e3a91aa3 --- /dev/null +++ b/src/ImageSharp/Advanced/IConfigurationProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for objects that can provide access to configuration. + /// + internal interface IConfigurationProvider + { + /// + /// Gets the configuration which allows altering default behaviour or extending the library. + /// + Configuration Configuration { get; } + } +} diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index ba8b13e2e8..079db42c07 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -17,6 +17,6 @@ namespace SixLabors.ImageSharp.Advanced /// The image. /// The pixel type. void Visit(Image image) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Advanced/IPixelSource.cs b/src/ImageSharp/Advanced/IPixelSource.cs index a321e877ba..f609b15d30 100644 --- a/src/ImageSharp/Advanced/IPixelSource.cs +++ b/src/ImageSharp/Advanced/IPixelSource.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; @@ -6,16 +6,27 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Advanced { + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + internal interface IPixelSource + { + /// + /// Gets the pixel buffer. + /// + Buffer2D PixelBuffer { get; } + } + /// /// Encapsulates the basic properties and methods required to manipulate images. /// /// The type of the pixel. internal interface IPixelSource - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Gets the pixel buffer. /// Buffer2D PixelBuffer { get; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs new file mode 100644 index 0000000000..980ed91a78 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row interval. + /// + public interface IRowIntervalOperation + { + /// + /// Invokes the method passing the row interval. + /// + /// The row interval. + void Invoke(in RowInterval rows); + } +} diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs new file mode 100644 index 0000000000..47fcf253eb --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row interval with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowIntervalOperation + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row interval and a buffer. + /// + /// The row interval. + /// The contiguous region of memory. + void Invoke(in RowInterval rows, Span span); + } +} diff --git a/src/ImageSharp/Advanced/IRowOperation.cs b/src/ImageSharp/Advanced/IRowOperation.cs new file mode 100644 index 0000000000..0a6065e4b1 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowOperation.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row. + /// + public interface IRowOperation + { + /// + /// Invokes the method passing the row y coordinate. + /// + /// The row y coordinate. + void Invoke(int y); + } +} diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs new file mode 100644 index 0000000000..7a13930fad --- /dev/null +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowOperation + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row and a buffer. + /// + /// The row y coordinate. + /// The contiguous region of memory. + void Invoke(int y, Span span); + } +} diff --git a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs new file mode 100644 index 0000000000..54ee069184 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines execution settings for methods in . + /// + public readonly struct ParallelExecutionSettings + { + /// + /// Default value for . + /// + public const int DefaultMinimumPixelsProcessedPerTask = 4096; + + /// + /// Initializes a new instance of the struct. + /// + /// The value used for initializing when using TPL. + /// The value for . + /// The . + public ParallelExecutionSettings( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + MemoryAllocator memoryAllocator) + { + // Shall be compatible with ParallelOptions.MaxDegreeOfParallelism: + // https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism + if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1) + { + throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); + } + + Guard.MustBeGreaterThan(minimumPixelsProcessedPerTask, 0, nameof(minimumPixelsProcessedPerTask)); + Guard.NotNull(memoryAllocator, nameof(memoryAllocator)); + + this.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; + this.MemoryAllocator = memoryAllocator; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The value used for initializing when using TPL. + /// The . + public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) + : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) + { + } + + /// + /// Gets the . + /// + public MemoryAllocator MemoryAllocator { get; } + + /// + /// Gets the value used for initializing when using TPL. + /// + public int MaxDegreeOfParallelism { get; } + + /// + /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL. + /// Launching tasks for pixel regions below this limit is not worth the overhead. + /// Initialized with by default, + /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.) + /// + public int MinimumPixelsProcessedPerTask { get; } + + /// + /// Creates a new instance of + /// having multiplied by + /// + /// The value to multiply with. + /// The modified . + public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) + { + Guard.MustBeGreaterThan(multiplier, 0, nameof(multiplier)); + + return new ParallelExecutionSettings( + this.MaxDegreeOfParallelism, + this.MinimumPixelsProcessedPerTask * multiplier, + this.MemoryAllocator); + } + + /// + /// Get the default for a + /// + /// The . + /// The . + public static ParallelExecutionSettings FromConfiguration(Configuration configuration) + { + return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs new file mode 100644 index 0000000000..3f0f77ca39 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing based on values defined + /// or . + /// Using this class is preferred over direct usage of utility methods. + /// + public static partial class ParallelRowIterator + { + private readonly struct RowOperationWrapper + where T : struct, IRowOperation + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperationWrapper( + int minY, + int maxY, + int stepY, + in T action) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + + for (int y = yMin; y < yMax; y++) + { + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.action).Invoke(y); + } + } + } + + private readonly struct RowOperationWrapper + where T : struct, IRowOperation + where TBuffer : unmanaged + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly int width; + private readonly MemoryAllocator allocator; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperationWrapper( + int minY, + int maxY, + int stepY, + int width, + MemoryAllocator allocator, + in T action) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + + using IMemoryOwner buffer = this.allocator.Allocate(this.width); + + Span span = buffer.Memory.Span; + + for (int y = yMin; y < yMax; y++) + { + Unsafe.AsRef(this.action).Invoke(y, span); + } + } + } + + private readonly struct RowIntervalOperationWrapper + where T : struct, IRowIntervalOperation + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper( + int minY, + int maxY, + int stepY, + in T operation) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.operation = operation; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + var rows = new RowInterval(yMin, yMax); + + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(in this.operation).Invoke(in rows); + } + } + + private readonly struct RowIntervalOperationWrapper + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly int width; + private readonly MemoryAllocator allocator; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper( + int minY, + int maxY, + int stepY, + int width, + MemoryAllocator allocator, + in T operation) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; + this.allocator = allocator; + this.operation = operation; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + var rows = new RowInterval(yMin, yMax); + + using IMemoryOwner buffer = this.allocator.Allocate(this.width); + + Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs new file mode 100644 index 0000000000..fb85de9863 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -0,0 +1,288 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing based on values defined + /// or . + /// Using this class is preferred over direct usage of utility methods. + /// + public static partial class ParallelRowIterator + { + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row operation to perform. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single row. + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowOperation + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row operation to perform. + /// The . + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowOperation + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowOperation + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowOperation + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using IMemoryOwner buffer = allocator.Allocate(width); + Span span = buffer.Memory.Span; + + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y, span); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The type of row operation to perform. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single . + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowIntervalOperation + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRowIntervals(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The type of row operation to perform. + /// The . + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRowIntervals( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowIntervalOperation + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + Unsafe.AsRef(in operation).Invoke(in rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRowIntervals(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRowIntervals( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + using IMemoryOwner buffer = allocator.Allocate(width); + + Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + + private static void ValidateRectangle(Rectangle rectangle) + { + Guard.MustBeGreaterThan( + rectangle.Width, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); + + Guard.MustBeGreaterThan( + rectangle.Height, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); + } + } +} diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs index 8811516c1c..240ce304d8 100644 --- a/src/ImageSharp/Color/Color.NamedColors.cs +++ b/src/ImageSharp/Color/Color.NamedColors.cs @@ -1,13 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; + namespace SixLabors.ImageSharp { /// /// Contains static named color values. + /// /// public readonly partial struct Color { + private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true); + /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// @@ -111,7 +117,7 @@ namespace SixLabors.ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Cyan = FromRgba(0, 255, 255, 255); + public static readonly Color Cyan = Aqua; /// /// Represents a matching the W3C definition that has an hex value of #00008B. @@ -138,6 +144,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGrey = DarkGray; + /// /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// @@ -174,9 +185,9 @@ namespace SixLabors.ImageSharp public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255); /// - /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// Represents a matching the W3C definition that has an hex value of #8FBC8F. /// - public static readonly Color DarkSeaGreen = FromRgba(143, 188, 139, 255); + public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255); /// /// Represents a matching the W3C definition that has an hex value of #483D8B. @@ -188,6 +199,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Color DarkSlateGrey = DarkSlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #00CED1. /// @@ -213,6 +229,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DimGray = FromRgba(105, 105, 105, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Color DimGrey = DimGray; + /// /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// @@ -273,6 +294,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Grey = Gray; + /// /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// @@ -353,6 +379,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color LightGreen = FromRgba(144, 238, 144, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGrey = LightGray; + /// /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// @@ -378,6 +409,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Color LightSlateGrey = LightSlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// @@ -406,7 +442,7 @@ namespace SixLabors.ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Magenta = FromRgba(255, 0, 255, 255); + public static readonly Color Magenta = Fuchsia; /// /// Represents a matching the W3C definition that has an hex value of #800000. @@ -643,6 +679,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color SlateGray = FromRgba(112, 128, 144, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Color SlateGrey = SlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// @@ -679,9 +720,9 @@ namespace SixLabors.ImageSharp public static readonly Color Tomato = FromRgba(255, 99, 71, 255); /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// Represents a matching the W3C definition that has an hex value of #00000000. /// - public static readonly Color Transparent = FromRgba(255, 255, 255, 0); + public static readonly Color Transparent = FromRgba(0, 0, 0, 0); /// /// Represents a matching the W3C definition that has an hex value of #40E0D0. @@ -717,5 +758,161 @@ namespace SixLabors.ImageSharp /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255); + + private static Dictionary CreateNamedColorsLookup() + { + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(AliceBlue), AliceBlue }, + { nameof(AntiqueWhite), AntiqueWhite }, + { nameof(Aqua), Aqua }, + { nameof(Aquamarine), Aquamarine }, + { nameof(Azure), Azure }, + { nameof(Beige), Beige }, + { nameof(Bisque), Bisque }, + { nameof(Black), Black }, + { nameof(BlanchedAlmond), BlanchedAlmond }, + { nameof(Blue), Blue }, + { nameof(BlueViolet), BlueViolet }, + { nameof(Brown), Brown }, + { nameof(BurlyWood), BurlyWood }, + { nameof(CadetBlue), CadetBlue }, + { nameof(Chartreuse), Chartreuse }, + { nameof(Chocolate), Chocolate }, + { nameof(Coral), Coral }, + { nameof(CornflowerBlue), CornflowerBlue }, + { nameof(Cornsilk), Cornsilk }, + { nameof(Crimson), Crimson }, + { nameof(Cyan), Cyan }, + { nameof(DarkBlue), DarkBlue }, + { nameof(DarkCyan), DarkCyan }, + { nameof(DarkGoldenrod), DarkGoldenrod }, + { nameof(DarkGray), DarkGray }, + { nameof(DarkGreen), DarkGreen }, + { nameof(DarkGrey), DarkGrey }, + { nameof(DarkKhaki), DarkKhaki }, + { nameof(DarkMagenta), DarkMagenta }, + { nameof(DarkOliveGreen), DarkOliveGreen }, + { nameof(DarkOrange), DarkOrange }, + { nameof(DarkOrchid), DarkOrchid }, + { nameof(DarkRed), DarkRed }, + { nameof(DarkSalmon), DarkSalmon }, + { nameof(DarkSeaGreen), DarkSeaGreen }, + { nameof(DarkSlateBlue), DarkSlateBlue }, + { nameof(DarkSlateGray), DarkSlateGray }, + { nameof(DarkSlateGrey), DarkSlateGrey }, + { nameof(DarkTurquoise), DarkTurquoise }, + { nameof(DarkViolet), DarkViolet }, + { nameof(DeepPink), DeepPink }, + { nameof(DeepSkyBlue), DeepSkyBlue }, + { nameof(DimGray), DimGray }, + { nameof(DimGrey), DimGrey }, + { nameof(DodgerBlue), DodgerBlue }, + { nameof(Firebrick), Firebrick }, + { nameof(FloralWhite), FloralWhite }, + { nameof(ForestGreen), ForestGreen }, + { nameof(Fuchsia), Fuchsia }, + { nameof(Gainsboro), Gainsboro }, + { nameof(GhostWhite), GhostWhite }, + { nameof(Gold), Gold }, + { nameof(Goldenrod), Goldenrod }, + { nameof(Gray), Gray }, + { nameof(Green), Green }, + { nameof(GreenYellow), GreenYellow }, + { nameof(Grey), Grey }, + { nameof(Honeydew), Honeydew }, + { nameof(HotPink), HotPink }, + { nameof(IndianRed), IndianRed }, + { nameof(Indigo), Indigo }, + { nameof(Ivory), Ivory }, + { nameof(Khaki), Khaki }, + { nameof(Lavender), Lavender }, + { nameof(LavenderBlush), LavenderBlush }, + { nameof(LawnGreen), LawnGreen }, + { nameof(LemonChiffon), LemonChiffon }, + { nameof(LightBlue), LightBlue }, + { nameof(LightCoral), LightCoral }, + { nameof(LightCyan), LightCyan }, + { nameof(LightGoldenrodYellow), LightGoldenrodYellow }, + { nameof(LightGray), LightGray }, + { nameof(LightGreen), LightGreen }, + { nameof(LightGrey), LightGrey }, + { nameof(LightPink), LightPink }, + { nameof(LightSalmon), LightSalmon }, + { nameof(LightSeaGreen), LightSeaGreen }, + { nameof(LightSkyBlue), LightSkyBlue }, + { nameof(LightSlateGray), LightSlateGray }, + { nameof(LightSlateGrey), LightSlateGrey }, + { nameof(LightSteelBlue), LightSteelBlue }, + { nameof(LightYellow), LightYellow }, + { nameof(Lime), Lime }, + { nameof(LimeGreen), LimeGreen }, + { nameof(Linen), Linen }, + { nameof(Magenta), Magenta }, + { nameof(Maroon), Maroon }, + { nameof(MediumAquamarine), MediumAquamarine }, + { nameof(MediumBlue), MediumBlue }, + { nameof(MediumOrchid), MediumOrchid }, + { nameof(MediumPurple), MediumPurple }, + { nameof(MediumSeaGreen), MediumSeaGreen }, + { nameof(MediumSlateBlue), MediumSlateBlue }, + { nameof(MediumSpringGreen), MediumSpringGreen }, + { nameof(MediumTurquoise), MediumTurquoise }, + { nameof(MediumVioletRed), MediumVioletRed }, + { nameof(MidnightBlue), MidnightBlue }, + { nameof(MintCream), MintCream }, + { nameof(MistyRose), MistyRose }, + { nameof(Moccasin), Moccasin }, + { nameof(NavajoWhite), NavajoWhite }, + { nameof(Navy), Navy }, + { nameof(OldLace), OldLace }, + { nameof(Olive), Olive }, + { nameof(OliveDrab), OliveDrab }, + { nameof(Orange), Orange }, + { nameof(OrangeRed), OrangeRed }, + { nameof(Orchid), Orchid }, + { nameof(PaleGoldenrod), PaleGoldenrod }, + { nameof(PaleGreen), PaleGreen }, + { nameof(PaleTurquoise), PaleTurquoise }, + { nameof(PaleVioletRed), PaleVioletRed }, + { nameof(PapayaWhip), PapayaWhip }, + { nameof(PeachPuff), PeachPuff }, + { nameof(Peru), Peru }, + { nameof(Pink), Pink }, + { nameof(Plum), Plum }, + { nameof(PowderBlue), PowderBlue }, + { nameof(Purple), Purple }, + { nameof(RebeccaPurple), RebeccaPurple }, + { nameof(Red), Red }, + { nameof(RosyBrown), RosyBrown }, + { nameof(RoyalBlue), RoyalBlue }, + { nameof(SaddleBrown), SaddleBrown }, + { nameof(Salmon), Salmon }, + { nameof(SandyBrown), SandyBrown }, + { nameof(SeaGreen), SeaGreen }, + { nameof(SeaShell), SeaShell }, + { nameof(Sienna), Sienna }, + { nameof(Silver), Silver }, + { nameof(SkyBlue), SkyBlue }, + { nameof(SlateBlue), SlateBlue }, + { nameof(SlateGray), SlateGray }, + { nameof(SlateGrey), SlateGrey }, + { nameof(Snow), Snow }, + { nameof(SpringGreen), SpringGreen }, + { nameof(SteelBlue), SteelBlue }, + { nameof(Tan), Tan }, + { nameof(Teal), Teal }, + { nameof(Thistle), Thistle }, + { nameof(Tomato), Tomato }, + { nameof(Transparent), Transparent }, + { nameof(Turquoise), Turquoise }, + { nameof(Violet), Violet }, + { nameof(Wheat), Wheat }, + { nameof(White), White }, + { nameof(WhiteSmoke), WhiteSmoke }, + { nameof(Yellow), Yellow }, + { nameof(YellowGreen), YellowGreen } + }; + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs index 768fe065cd..2948b4c52c 100644 --- a/src/ImageSharp/Color/Color.WernerPalette.cs +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -20,116 +20,116 @@ namespace SixLabors.ImageSharp private static Color[] CreateWernerPalette() => new[] { - FromHex("#f1e9cd"), - FromHex("#f2e7cf"), - FromHex("#ece6d0"), - FromHex("#f2eacc"), - FromHex("#f3e9ca"), - FromHex("#f2ebcd"), - FromHex("#e6e1c9"), - FromHex("#e2ddc6"), - FromHex("#cbc8b7"), - FromHex("#bfbbb0"), - FromHex("#bebeb3"), - FromHex("#b7b5ac"), - FromHex("#bab191"), - FromHex("#9c9d9a"), - FromHex("#8a8d84"), - FromHex("#5b5c61"), - FromHex("#555152"), - FromHex("#413f44"), - FromHex("#454445"), - FromHex("#423937"), - FromHex("#433635"), - FromHex("#252024"), - FromHex("#241f20"), - FromHex("#281f3f"), - FromHex("#1c1949"), - FromHex("#4f638d"), - FromHex("#383867"), - FromHex("#5c6b8f"), - FromHex("#657abb"), - FromHex("#6f88af"), - FromHex("#7994b5"), - FromHex("#6fb5a8"), - FromHex("#719ba2"), - FromHex("#8aa1a6"), - FromHex("#d0d5d3"), - FromHex("#8590ae"), - FromHex("#3a2f52"), - FromHex("#39334a"), - FromHex("#6c6d94"), - FromHex("#584c77"), - FromHex("#533552"), - FromHex("#463759"), - FromHex("#bfbac0"), - FromHex("#77747f"), - FromHex("#4a475c"), - FromHex("#b8bfaf"), - FromHex("#b2b599"), - FromHex("#979c84"), - FromHex("#5d6161"), - FromHex("#61ac86"), - FromHex("#a4b6a7"), - FromHex("#adba98"), - FromHex("#93b778"), - FromHex("#7d8c55"), - FromHex("#33431e"), - FromHex("#7c8635"), - FromHex("#8e9849"), - FromHex("#c2c190"), - FromHex("#67765b"), - FromHex("#ab924b"), - FromHex("#c8c76f"), - FromHex("#ccc050"), - FromHex("#ebdd99"), - FromHex("#ab9649"), - FromHex("#dbc364"), - FromHex("#e6d058"), - FromHex("#ead665"), - FromHex("#d09b2c"), - FromHex("#a36629"), - FromHex("#a77d35"), - FromHex("#f0d696"), - FromHex("#d7c485"), - FromHex("#f1d28c"), - FromHex("#efcc83"), - FromHex("#f3daa7"), - FromHex("#dfa837"), - FromHex("#ebbc71"), - FromHex("#d17c3f"), - FromHex("#92462f"), - FromHex("#be7249"), - FromHex("#bb603c"), - FromHex("#c76b4a"), - FromHex("#a75536"), - FromHex("#b63e36"), - FromHex("#b5493a"), - FromHex("#cd6d57"), - FromHex("#711518"), - FromHex("#e9c49d"), - FromHex("#eedac3"), - FromHex("#eecfbf"), - FromHex("#ce536b"), - FromHex("#b74a70"), - FromHex("#b7757c"), - FromHex("#612741"), - FromHex("#7a4848"), - FromHex("#3f3033"), - FromHex("#8d746f"), - FromHex("#4d3635"), - FromHex("#6e3b31"), - FromHex("#864735"), - FromHex("#553d3a"), - FromHex("#613936"), - FromHex("#7a4b3a"), - FromHex("#946943"), - FromHex("#c39e6d"), - FromHex("#513e32"), - FromHex("#8b7859"), - FromHex("#9b856b"), - FromHex("#766051"), - FromHex("#453b32") + ParseHex("#f1e9cd"), + ParseHex("#f2e7cf"), + ParseHex("#ece6d0"), + ParseHex("#f2eacc"), + ParseHex("#f3e9ca"), + ParseHex("#f2ebcd"), + ParseHex("#e6e1c9"), + ParseHex("#e2ddc6"), + ParseHex("#cbc8b7"), + ParseHex("#bfbbb0"), + ParseHex("#bebeb3"), + ParseHex("#b7b5ac"), + ParseHex("#bab191"), + ParseHex("#9c9d9a"), + ParseHex("#8a8d84"), + ParseHex("#5b5c61"), + ParseHex("#555152"), + ParseHex("#413f44"), + ParseHex("#454445"), + ParseHex("#423937"), + ParseHex("#433635"), + ParseHex("#252024"), + ParseHex("#241f20"), + ParseHex("#281f3f"), + ParseHex("#1c1949"), + ParseHex("#4f638d"), + ParseHex("#383867"), + ParseHex("#5c6b8f"), + ParseHex("#657abb"), + ParseHex("#6f88af"), + ParseHex("#7994b5"), + ParseHex("#6fb5a8"), + ParseHex("#719ba2"), + ParseHex("#8aa1a6"), + ParseHex("#d0d5d3"), + ParseHex("#8590ae"), + ParseHex("#3a2f52"), + ParseHex("#39334a"), + ParseHex("#6c6d94"), + ParseHex("#584c77"), + ParseHex("#533552"), + ParseHex("#463759"), + ParseHex("#bfbac0"), + ParseHex("#77747f"), + ParseHex("#4a475c"), + ParseHex("#b8bfaf"), + ParseHex("#b2b599"), + ParseHex("#979c84"), + ParseHex("#5d6161"), + ParseHex("#61ac86"), + ParseHex("#a4b6a7"), + ParseHex("#adba98"), + ParseHex("#93b778"), + ParseHex("#7d8c55"), + ParseHex("#33431e"), + ParseHex("#7c8635"), + ParseHex("#8e9849"), + ParseHex("#c2c190"), + ParseHex("#67765b"), + ParseHex("#ab924b"), + ParseHex("#c8c76f"), + ParseHex("#ccc050"), + ParseHex("#ebdd99"), + ParseHex("#ab9649"), + ParseHex("#dbc364"), + ParseHex("#e6d058"), + ParseHex("#ead665"), + ParseHex("#d09b2c"), + ParseHex("#a36629"), + ParseHex("#a77d35"), + ParseHex("#f0d696"), + ParseHex("#d7c485"), + ParseHex("#f1d28c"), + ParseHex("#efcc83"), + ParseHex("#f3daa7"), + ParseHex("#dfa837"), + ParseHex("#ebbc71"), + ParseHex("#d17c3f"), + ParseHex("#92462f"), + ParseHex("#be7249"), + ParseHex("#bb603c"), + ParseHex("#c76b4a"), + ParseHex("#a75536"), + ParseHex("#b63e36"), + ParseHex("#b5493a"), + ParseHex("#cd6d57"), + ParseHex("#711518"), + ParseHex("#e9c49d"), + ParseHex("#eedac3"), + ParseHex("#eecfbf"), + ParseHex("#ce536b"), + ParseHex("#b74a70"), + ParseHex("#b7757c"), + ParseHex("#612741"), + ParseHex("#7a4848"), + ParseHex("#3f3033"), + ParseHex("#8d746f"), + ParseHex("#4d3635"), + ParseHex("#6e3b31"), + ParseHex("#864735"), + ParseHex("#553d3a"), + ParseHex("#613936"), + ParseHex("#7a4b3a"), + ParseHex("#946943"), + ParseHex("#c39e6d"), + ParseHex("#513e32"), + ParseHex("#8b7859"), + ParseHex("#9b856b"), + ParseHex("#766051"), + ParseHex("#453b32") }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 4bdbe088ca..e0f9d1e8d1 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -95,21 +95,102 @@ namespace SixLabors.ImageSharp public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); /// - /// Creates a new instance from the string representing a color in hexadecimal form. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. /// - /// Returns a that represents the color defined by the provided RGBA hex string. + /// + /// The . + /// [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromHex(string hex) + public static Color ParseHex(string hex) { - Rgba32 rgba = Rgba32.FromHex(hex); + var rgba = Rgba32.ParseHex(hex); return new Color(rgba); } + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Color result) + { + result = default; + + if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) + { + result = new Color(rgba); + return true; + } + + return false; + } + + /// + /// Creates a new instance of the struct + /// from the given input string. + /// + /// + /// The name of the color or the hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Color Parse(string input) + { + Guard.NotNull(input, nameof(input)); + + if (!TryParse(input, out Color color)) + { + throw new ArgumentException("Input string is not in the correct format.", nameof(input)); + } + + return color; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given input string. + /// + /// + /// The name of the color or the hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + public static bool TryParse(string input, out Color result) + { + result = default; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) + { + return true; + } + + return TryParseHex(input, out result); + } + /// /// Alters the alpha channel of the color, returning a new instance. /// @@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp /// The color having it's alpha channel altered. public Color WithAlpha(float alpha) { - Vector4 v = (Vector4)this; + var v = (Vector4)this; v.W = alpha; return new Color(v); } @@ -133,20 +214,37 @@ namespace SixLabors.ImageSharp public override string ToString() => this.ToHex(); /// - /// Converts the color instance to an - /// implementation defined by . + /// Converts the color instance to a specified type. /// /// The pixel type to convert to. /// The pixel value. [MethodImpl(InliningOptions.ShortMethod)] public TPixel ToPixel() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; pixel.FromRgba64(this.data); return pixel; } + /// + /// Bulk converts a span of to a span of a specified type. + /// + /// The pixel type to convert to. + /// The configuration. + /// The source color span. + /// The destination pixel span. + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToPixel( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); + PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(Color other) @@ -166,19 +264,5 @@ namespace SixLabors.ImageSharp { return this.data.PackedValue.GetHashCode(); } - - /// - /// Bulk convert a span of to a span of a specified pixel type. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ToPixel( - Configuration configuration, - ReadOnlySpan source, - Span destination) - where TPixel : struct, IPixel - { - ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); - PixelOperations.Instance.FromRgba64(Configuration.Default, rgba64Span, destination); - } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs deleted file mode 100644 index 8e14274f66..0000000000 --- a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -// ReSharper disable CompareOfFloatsByEqualityOperator -namespace SixLabors.ImageSharp.ColorSpaces -{ - /// - /// Represents the coordinates of CIEXY chromaticity space. - /// - public readonly struct CieXyChromaticityCoordinates : IEquatable - { - /// - /// Gets the chromaticity X-coordinate. - /// - /// - /// Ranges usually from 0 to 1. - /// - public readonly float X; - - /// - /// Gets the chromaticity Y-coordinate - /// - /// - /// Ranges usually from 0 to 1. - /// - public readonly float Y; - - /// - /// Initializes a new instance of the struct. - /// - /// Chromaticity coordinate x (usually from 0 to 1) - /// Chromaticity coordinate y (usually from 0 to 1) - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyChromaticityCoordinates(float x, float y) - { - this.X = x; - this.Y = y; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => !left.Equals(right); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); - - /// - public override string ToString() => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); - - /// - public override bool Equals(object obj) => obj is CieXyChromaticityCoordinates other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieXyChromaticityCoordinates other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs index c2331c3798..5229cf14fb 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.ColorSpaces [MethodImpl(InliningOptions.ShortMethod)] public Cmyk(Vector4 vector) { - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); this.C = vector.X; this.M = vector.Y; this.Y = vector.Z; diff --git a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs index 80b8aee7e7..981e32f8ea 100644 --- a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -35,4 +35,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding public static float Compress(float channel) => channel <= CieConstants.Epsilon ? (channel * CieConstants.Kappa) / 100F : (1.16F * MathF.Pow(channel, 0.3333333F)) - 0.16F; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs index 892c0d5e38..2b9073814f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -157,4 +155,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(linearOutput); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs index 273c9be912..69d0c1bed3 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs index 7f4abfa7bb..52f8724305 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -447,4 +445,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs index 6ca40af035..2588561c84 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -447,4 +445,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs index b790712c50..867b44a784 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs index d03c10a01d..344b83254c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs index 21d576fb41..8362432ab9 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -475,4 +473,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs index 00e20e25b4..3ff9ac85fd 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs index 76175f1cba..7c74363919 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs index e64beb3e49..e60ad5e20c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -440,4 +438,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs index 4be3f0079d..b2c23b5a49 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -248,7 +246,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into . + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -435,4 +433,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(rgb); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs index fc5665e5c1..e83dcb1258 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -438,4 +436,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.Adapt(rgb); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs index 68cd34bf2e..4ea1d8d8fb 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -407,4 +405,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public YCbCr ToYCbCr(in Rgb color) => YCbCrAndRgbConverter.Convert(color); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs index bcbd64c77a..05d3b2f2db 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { @@ -58,4 +57,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(this.targetRgbWorkingSpace); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs index 65fe799949..27dd989ade 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { @@ -52,4 +51,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// public Matrix4x4 LmsAdaptationMatrix { get; set; } = CieXyzAndLmsConverter.DefaultTransformationMatrix; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs new file mode 100644 index 0000000000..1e774fe67a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +// ReSharper disable CompareOfFloatsByEqualityOperator +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Represents the coordinates of CIEXY chromaticity space. + /// + public readonly struct CieXyChromaticityCoordinates : IEquatable + { + /// + /// Gets the chromaticity X-coordinate. + /// + /// + /// Ranges usually from 0 to 1. + /// + public readonly float X; + + /// + /// Gets the chromaticity Y-coordinate + /// + /// + /// Ranges usually from 0 to 1. + /// + public readonly float Y; + + /// + /// Initializes a new instance of the struct. + /// + /// Chromaticity coordinate x (usually from 0 to 1) + /// Chromaticity coordinate y (usually from 0 to 1) + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyChromaticityCoordinates(float x, float y) + { + this.X = x; + this.Y = y; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + + /// + public override string ToString() => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieXyChromaticityCoordinates other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyChromaticityCoordinates other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs index 40d8c5bc69..014ca1abbd 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs index 2b859205a0..e1bf04aa76 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -38,4 +38,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new CieLch(l, c, hDegrees, input.WhitePoint); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs index dfbbc8f0c7..bafd0df47a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -41,4 +41,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new CieXyz(xyz); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs index ba5b8bfb79..2dae2669fb 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -30,4 +30,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new CieLuv(l, u, v, input.WhitePoint); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs index 3c7d356a5e..29fdae69c9 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . @@ -38,4 +38,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new CieLchuv(l, c, hDegrees, input.WhitePoint); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs index 33f3ec3d3e..09040c98c6 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs index 7767b7b448..f704d1a348 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between CIE XYZ and CIE xyY. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs index 1cd511e819..68f6c495d4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// The base class for converting between and color spaces. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs index f860652b18..a22b097d2b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs @@ -4,7 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs index c155087ff5..4f4aa84820 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs index 7f2bb0cf6a..96ec96b49c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Converts from to . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs index f21235d06c..881d3d9198 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs index 6497e3060c..25558537a9 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs @@ -4,7 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs index 0700dab43a..8e92ced949 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs @@ -5,7 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and . diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs index 97465e526a..5dbd23b8b8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between HSL and Rgb diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs index 20ada7e7dd..04ab0480b4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between HSV and Rgb @@ -127,4 +127,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new Hsv(h, s, v); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs index 4d6808e6c0..795db7e2c9 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and @@ -36,4 +36,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new CieXyz(x, y, z); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs index bdf451cd3c..1058170758 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Provides base methods for converting between and color spaces. @@ -74,4 +74,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation }; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs index 21a96071af..adaad50fe0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and @@ -50,4 +50,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return new CieXyz(vector); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs index 8454430935..54081b6393 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and . @@ -25,4 +25,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation workingSpace: input.WorkingSpace); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs index 4ddbe42e54..bc8c6f0301 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between Rgb and LinearRgb. @@ -25,4 +25,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation workingSpace: input.WorkingSpace); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs index ee15ffa508..0821c05c34 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs @@ -5,7 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Color converter between and diff --git a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs similarity index 100% rename from src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs index 37e4b1a1a6..8b8e4ab57a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Matrices used for transformation from to , defining the cone response domain. @@ -131,4 +131,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation M44 = 1F }); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs index 8871d04656..03378f431b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Represents the chromaticity coordinates of RGB primaries. diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs new file mode 100644 index 0000000000..7589a1d570 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Implementation of the von Kries chromatic adaptation model. + /// + /// + /// Transformation described here: + /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + /// + public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation + { + private readonly CieXyzAndLmsConverter converter; + + /// + /// Initializes a new instance of the class. + /// + public VonKriesChromaticAdaptation() + : this(new CieXyzAndLmsConverter()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The transformation matrix used for the conversion (definition of the cone response domain). + /// + /// + public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix) + : this(new CieXyzAndLmsConverter(transformationMatrix)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The color converter + internal VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) => this.converter = converter; + + /// + public CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint) + { + if (sourceWhitePoint.Equals(destinationWhitePoint)) + { + return source; + } + + Lms sourceColorLms = this.converter.Convert(source); + Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); + Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); + + Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); + var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); + + return this.converter.Convert(targetColorLms); + } + + /// + public void Transform( + ReadOnlySpan source, + Span destination, + CieXyz sourceWhitePoint, + in CieXyz destinationWhitePoint) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + if (sourceWhitePoint.Equals(destinationWhitePoint)) + { + source.CopyTo(destination.Slice(0, count)); + return; + } + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + + Lms sourceColorLms = this.converter.Convert(sp); + Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); + Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); + + Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); + var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); + + dp = this.converter.Convert(targetColorLms); + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs index 6caca54cdc..c7020723be 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// The gamma working space. @@ -63,4 +63,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation this.ChromaticityCoordinates, this.Gamma); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs index a2eb42ad0b..d9c4365270 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// L* working space. @@ -29,4 +29,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation [MethodImpl(InliningOptions.ShortMethod)] public override float Expand(float channel) => LCompanding.Expand(channel); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs index a794b3dda7..4698534db4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. @@ -29,4 +29,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation [MethodImpl(InliningOptions.ShortMethod)] public override float Expand(float channel) => Rec2020Companding.Expand(channel); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs index ffa9699bc5..80b635cadc 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Rec. 709 (ITU-R Recommendation BT.709) working space. @@ -29,4 +29,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation [MethodImpl(InliningOptions.ShortMethod)] public override float Expand(float channel) => Rec709Companding.Expand(channel); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs index a97ae22b18..2e5a5a4eb2 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Base class for all implementations of . @@ -81,4 +81,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation return HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs index c3d850251a..c9246f5100 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// The sRgb working space. diff --git a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs deleted file mode 100644 index a4d96d19e7..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion -{ - /// - /// Implementation of the von Kries chromatic adaptation model. - /// - /// - /// Transformation described here: - /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - /// - public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation - { - private readonly CieXyzAndLmsConverter converter; - - /// - /// Initializes a new instance of the class. - /// - public VonKriesChromaticAdaptation() - : this(new CieXyzAndLmsConverter()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The transformation matrix used for the conversion (definition of the cone response domain). - /// - /// - public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix) - : this(new CieXyzAndLmsConverter(transformationMatrix)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color converter - internal VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) => this.converter = converter; - - /// - public CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint) - { - if (sourceWhitePoint.Equals(destinationWhitePoint)) - { - return source; - } - - Lms sourceColorLms = this.converter.Convert(source); - Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); - Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); - - Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); - var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); - - return this.converter.Convert(targetColorLms); - } - - /// - public void Transform( - ReadOnlySpan source, - Span destination, - CieXyz sourceWhitePoint, - in CieXyz destinationWhitePoint) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - if (sourceWhitePoint.Equals(destinationWhitePoint)) - { - source.CopyTo(destination.Slice(0, count)); - return; - } - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - - Lms sourceColorLms = this.converter.Convert(sp); - Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); - Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); - - Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); - var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); - - dp = this.converter.Convert(targetColorLms); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs index 7ef50e9c41..c120ef1141 100644 --- a/src/ImageSharp/ColorSpaces/LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.ColorSpaces.Conversion; namespace SixLabors.ImageSharp.ColorSpaces { @@ -143,4 +143,4 @@ namespace SixLabors.ImageSharp.ColorSpaces && this.B.Equals(other.B); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs index bb71deba3b..3c26b77332 100644 --- a/src/ImageSharp/ColorSpaces/Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.ColorSpaces @@ -164,4 +164,4 @@ namespace SixLabors.ImageSharp.ColorSpaces && this.B.Equals(other.B); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs index 3f40fa4f58..152c7ee0bc 100644 --- a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -1,8 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.ColorSpaces.Companding; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.ColorSpaces.Conversion; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.ColorSpaces @@ -112,4 +112,4 @@ namespace SixLabors.ImageSharp.ColorSpaces /// public static readonly RgbWorkingSpace WideGamutRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 8b9dbe1b80..4028b70b0e 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp { /// /// The exception that is thrown when the library tries to load - /// an image, which has an invalid format. + /// an image, which has format or content that is invalid or unsupported by ImageSharp. /// public class ImageFormatException : Exception { diff --git a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs index 3c75a6418a..ccd0b71e54 100644 --- a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp /// public sealed class ImageProcessingException : Exception { + /// + /// Initializes a new instance of the class. + /// + public ImageProcessingException() + { + } + /// /// Initializes a new instance of the class with the name of the /// parameter that causes this exception. @@ -32,4 +39,4 @@ namespace SixLabors.ImageSharp { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs new file mode 100644 index 0000000000..8be13919f1 --- /dev/null +++ b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp +{ + /// + /// The exception that is thrown when the library tries to load + /// an image which contains invalid content. + /// + public sealed class InvalidImageContentException : ImageFormatException + { + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public InvalidImageContentException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public InvalidImageContentException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs index 3c8570a2a4..1fe2a24c77 100644 --- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp /// /// Restricts a to be within a specified range. /// - /// The The value to clamp. + /// The value to clamp. /// The minimum value. If value is less than min, min will be returned. /// The maximum value. If value is greater than max, max will be returned. /// @@ -137,4 +137,4 @@ namespace SixLabors.ImageSharp return value; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Extensions/EncoderExtensions.cs b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs index 59c878485d..87aaa93a9f 100644 --- a/src/ImageSharp/Common/Extensions/EncoderExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -#if !NETCOREAPP2_1 +#if !SUPPORTS_ENCODING_STRING using System; using System.Text; @@ -32,4 +32,4 @@ namespace SixLabors.ImageSharp } } } -#endif \ No newline at end of file +#endif diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index cff6e3b601..983a1eb8b3 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Common +namespace SixLabors.ImageSharp { /// /// Encapsulates a series of time saving extension methods to the interface. @@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common /// /// Generates a sequence of integral numbers within a specified range. /// - /// - /// The start index, inclusive. - /// + /// The start index, inclusive. /// /// A method that has one parameter and returns a calculating the end index. /// - /// - /// The incremental step. - /// + /// The incremental step. /// /// The that contains a range of sequential integral numbers. /// @@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 6af09b220a..5d86682571 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; - using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; +#if !SUPPORTS_SPAN_STREAM +using System.Buffers; +#endif namespace SixLabors.ImageSharp { @@ -15,7 +15,6 @@ namespace SixLabors.ImageSharp /// internal static class StreamExtensions { -#if NETCOREAPP2_1 /// /// Writes data from a stream into the provided buffer. /// @@ -24,23 +23,18 @@ namespace SixLabors.ImageSharp /// The offset within the buffer to begin writing. /// The number of bytes to write to the stream. public static void Write(this Stream stream, Span buffer, int offset, int count) - { - stream.Write(buffer.Slice(offset, count)); - } + => stream.Write(buffer.Slice(offset, count)); /// /// Reads data from a stream into the provided buffer. /// /// The stream. - /// The buffer.. + /// The buffer. /// The offset within the buffer where the bytes are read into. /// The number of bytes, if available, to read. /// The actual number of bytes read. public static int Read(this Stream stream, Span buffer, int offset, int count) - { - return stream.Read(buffer.Slice(offset, count)); - } -#endif + => stream.Read(buffer.Slice(offset, count)); /// /// Skips the number of bytes in the given stream. @@ -75,17 +69,39 @@ namespace SixLabors.ImageSharp } public static void Read(this Stream stream, IManagedByteBuffer buffer) - { - stream.Read(buffer.Array, 0, buffer.Length()); - } + => stream.Read(buffer.Array, 0, buffer.Length()); public static void Write(this Stream stream, IManagedByteBuffer buffer) + => stream.Write(buffer.Array, 0, buffer.Length()); + +#if !SUPPORTS_SPAN_STREAM + // This is a port of the CoreFX implementation and is MIT Licensed: + // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 + public static int Read(this Stream stream, Span buffer) { - stream.Write(buffer.Array, 0, buffer.Length()); + // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, + // in order to match the signature of the framework method that exists in + // .NET Core. + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + int numRead = stream.Read(sharedBuffer, 0, buffer.Length); + if ((uint)numRead > (uint)buffer.Length) + { + throw new IOException("Stream was too long."); + } + + new Span(sharedBuffer, 0, numRead).CopyTo(buffer); + return numRead; + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } } -#if NET472 || NETSTANDARD1_3 || NETSTANDARD2_0 - // This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770 + // This is a port of the CoreFX implementation and is MIT Licensed: + // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L775 public static void Write(this Stream stream, ReadOnlySpan buffer) { // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs index 0c22aa68ff..312ab388d7 100644 --- a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs +++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp { @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ComplexVector4 vector = default; int kernelLength = kernel.Length; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp } /// - /// Computes the sum of vectors in weighted by the kernel weight values. + /// Computes the sum of vectors in weighted by the kernel weight values and accumulates the partial results. /// /// The 1D convolution kernel. /// The source frame. @@ -73,16 +73,20 @@ namespace SixLabors.ImageSharp /// The maximum working area row. /// The minimum working area column. /// The maximum working area column. - public static void Convolve4( + /// The weight factor for the real component of the complex pixel values. + /// The weight factor for the imaginary component of the complex pixel values. + public static void Convolve4AndAccumulatePartials( Span kernel, Buffer2D sourceValues, - Span targetRow, + Span targetRow, int row, int column, int minRow, int maxRow, int minColumn, - int maxColumn) + int maxColumn, + float z, + float w) { ComplexVector4 vector = default; int kernelLength = kernel.Length; @@ -99,7 +103,7 @@ namespace SixLabors.ImageSharp vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX)); } - targetRow[column] = vector; + targetRow[column] += vector.WeightedSum(z, w); } } } diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index c5c9ddebe1..462eeb3021 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -6,7 +6,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp { @@ -43,7 +42,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Convolve2DImpl( in matrixY, @@ -60,7 +59,7 @@ namespace SixLabors.ImageSharp ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -91,7 +90,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Convolve2DImpl( in matrixY, @@ -106,7 +105,7 @@ namespace SixLabors.ImageSharp out Vector4 vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -122,7 +121,7 @@ namespace SixLabors.ImageSharp int minColumn, int maxColumn, out Vector4 vector) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Vector4 vectorY = default; Vector4 vectorX = default; @@ -141,7 +140,7 @@ namespace SixLabors.ImageSharp { int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); - Vector4Utils.Premultiply(ref currentColor); + Vector4Utilities.Premultiply(ref currentColor); vectorX += matrixX[y, x] * currentColor; vectorY += matrixY[y, x] * currentColor; @@ -176,7 +175,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Vector4 vector = default; @@ -194,7 +193,7 @@ namespace SixLabors.ImageSharp ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -223,7 +222,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Vector4 vector = default; @@ -239,7 +238,7 @@ namespace SixLabors.ImageSharp ref vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -254,7 +253,7 @@ namespace SixLabors.ImageSharp int minColumn, int maxColumn, ref Vector4 vector) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int matrixHeight = matrix.Rows; int matrixWidth = matrix.Columns; @@ -271,7 +270,7 @@ namespace SixLabors.ImageSharp { int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); - Vector4Utils.Premultiply(ref currentColor); + Vector4Utilities.Premultiply(ref currentColor); vector += matrix[y, x] * currentColor; } } diff --git a/src/ImageSharp/Common/Helpers/EnumUtils.cs b/src/ImageSharp/Common/Helpers/EnumUtils.cs new file mode 100644 index 0000000000..a98b7f84c6 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/EnumUtils.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Common utility methods for working with enums. + /// + internal static class EnumUtils + { + /// + /// Converts the numeric representation of the enumerated constants to an equivalent enumerated object. + /// + /// The type of enum + /// The value to parse + /// The default value to return. + /// The . + public static TEnum Parse(int value, TEnum defaultValue) + where TEnum : Enum + { + foreach (TEnum enumValue in Enum.GetValues(typeof(TEnum))) + { + TEnum current = enumValue; + if (value == Unsafe.As(ref current)) + { + return enumValue; + } + } + + return defaultValue; + } + + /// + /// Returns a value indicating whether the given enum has a flag of the given value. + /// + /// The type of enum. + /// The value. + /// The flag. + /// The . + public static bool HasFlag(TEnum value, TEnum flag) + where TEnum : Enum + { + uint flagValue = Unsafe.As(ref flag); + return (Unsafe.As(ref value) & flagValue) == flagValue; + } + } +} diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs new file mode 100644 index 0000000000..3ab1b199a6 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp; + +namespace SixLabors +{ + internal static partial class Guard + { + /// + /// Ensures that the value is a value type. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// is not a value type. + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeValueType(TValue value, string parameterName) + { + if (!value.GetType().GetTypeInfo().IsValueType) + { + ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName); + } + } + } +} diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 7460c9cac1..fb1f88a2da 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -14,6 +14,20 @@ namespace SixLabors.ImageSharp /// internal static class ImageMaths { + /// + /// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709. + /// + private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f); + + /// + /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709. + /// + /// The vector to get the luminance from. + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels) + => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1)); + /// /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. /// @@ -23,7 +37,7 @@ namespace SixLabors.ImageSharp /// The . [MethodImpl(InliningOptions.ShortMethod)] public static byte Get8BitBT709Luminance(byte r, byte g, byte b) => - (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5f); + (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); /// /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. @@ -34,7 +48,7 @@ namespace SixLabors.ImageSharp /// The . [MethodImpl(InliningOptions.ShortMethod)] public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) => - (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); /// /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. @@ -45,7 +59,7 @@ namespace SixLabors.ImageSharp /// The . [MethodImpl(InliningOptions.ShortMethod)] public static ushort Get16BitBT709Luminance(float r, float g, float b) => - (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); /// /// Scales a value from a 16 bit to it's 8 bit equivalent. @@ -228,40 +242,6 @@ namespace SixLabors.ImageSharp return 1F; } - /// - /// Returns the result of a B-C filter against the given value. - /// - /// - /// The value to process. - /// The B-Spline curve variable. - /// The Cardinal curve variable. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float GetBcValue(float x, float b, float c) - { - if (x < 0F) - { - x = -x; - } - - float temp = x * x; - if (x < 1F) - { - x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); - return x / 6F; - } - - if (x < 2F) - { - x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); - return x / 6F; - } - - return 0F; - } - /// /// Gets the bounding from the given points. /// @@ -289,7 +269,7 @@ namespace SixLabors.ImageSharp /// The . /// public static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int width = bitmap.Width; int height = bitmap.Height; @@ -379,7 +359,7 @@ namespace SixLabors.ImageSharp } } - return height; + return width; } topLeft.Y = GetMinY(bitmap); diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs index 069a426d75..895b6250f6 100644 --- a/src/ImageSharp/Common/Helpers/InliningOptions.cs +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN! @@ -13,10 +13,16 @@ namespace SixLabors.ImageSharp internal static class InliningOptions { #if PROFILING + public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; #else +#if SUPPORTS_HOTPATH + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; +#else + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining; +#endif public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; #endif public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs new file mode 100644 index 0000000000..ea1ffba05a --- /dev/null +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +#if SUPPORTS_RUNTIME_INTRINSICS + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp +{ + internal static partial class SimdUtils + { + public static class Avx2Intrinsics + { + private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 }; + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void NormalizedFloatToByteSaturateReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (Avx2.IsSupported) + { + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + NormalizedFloatToByteSaturate( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + } + + /// + /// Implementation of , which is faster on new .NET runtime. + /// + /// + /// Implementation is based on MagicScaler code: + /// https://github.com/saucecontrol/PhotoSauce/blob/a9bd6e5162d2160419f0cf743fd4f536c079170b/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L453-L477 + /// + internal static void NormalizedFloatToByteSaturate( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, Vector256.Count); + + int n = dest.Length / Vector256.Count; + + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + var maxBytes = Vector256.Create(255f); + ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); + Vector256 mask = Unsafe.As>(ref maskBase); + + for (int i = 0; i < n; i++) + { + ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); + + Vector256 f0 = s; + Vector256 f1 = Unsafe.Add(ref s, 1); + Vector256 f2 = Unsafe.Add(ref s, 2); + Vector256 f3 = Unsafe.Add(ref s, 3); + + Vector256 w0 = ConvertToInt32(f0, maxBytes); + Vector256 w1 = ConvertToInt32(f1, maxBytes); + Vector256 w2 = ConvertToInt32(f2, maxBytes); + Vector256 w3 = ConvertToInt32(f3, maxBytes); + + Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); + Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); + Vector256 b = Avx2.PackUnsignedSaturate(u0, u1); + b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte(); + + Unsafe.Add(ref destBase, i) = b; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(vf, scale); + return Avx.ConvertToVector256Int32(vf); + } + } + } +} +#endif diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs index bc07fbf317..1099678f76 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs @@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp /// public static class BasicIntrinsics256 { - public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; + public static bool IsAvailable { get; } = HasVector8; #if !SUPPORTS_EXTENDED_INTRINSICS /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat( + ByteToNormalizedFloat( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + NormalizedFloatToByteSaturate(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); source = source.Slice(adjustedCount); dest = dest.Slice(adjustedCount); @@ -78,15 +78,15 @@ namespace SixLabors.ImageSharp #endif /// - /// SIMD optimized implementation for . + /// SIMD optimized implementation for . /// Works only with span Length divisible by 8. /// Implementation adapted from: /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions /// http://stackoverflow.com/a/536278 /// - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat)); + VerifyHasVector8(nameof(ByteToNormalizedFloat)); VerifySpanInput(source, dest, 8); var bVec = new Vector(256.0f / 255.0f); @@ -94,17 +94,17 @@ namespace SixLabors.ImageSharp var magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f var mask = new Vector(255); - ref Octet.OfByte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Octet.OfUInt32 destBaseAsWideOctet = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet destBaseAsWideOctet = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsWideOctet); + ref Vector destBaseAsFloat = ref Unsafe.As, Vector>(ref destBaseAsWideOctet); int n = dest.Length / 8; for (int i = 0; i < n; i++) { - ref Octet.OfByte s = ref Unsafe.Add(ref sourceBase, i); - ref Octet.OfUInt32 d = ref Unsafe.Add(ref destBaseAsWideOctet, i); + ref Octet s = ref Unsafe.Add(ref sourceBase, i); + ref Octet d = ref Unsafe.Add(ref destBaseAsWideOctet, i); d.LoadFrom(ref s); } @@ -124,11 +124,11 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of which is faster on older runtimes. + /// Implementation of which is faster on older runtimes. /// - internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows)); + VerifyHasVector8(nameof(NormalizedFloatToByteSaturate)); VerifySpanInput(source, dest, 8); if (source.Length == 0) @@ -137,17 +137,17 @@ namespace SixLabors.ImageSharp } ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); int n = source.Length / 8; var magick = new Vector(32768.0f); var scale = new Vector(255f) / new Vector(256f); // need to copy to a temporary struct, because - // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) + // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report - var temp = default(Octet.OfUInt32); - ref Vector tempRef = ref Unsafe.As>(ref temp); + var temp = default(Octet); + ref Vector tempRef = ref Unsafe.As, Vector>(ref temp); for (int i = 0; i < n; i++) { @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp x = (x * scale) + magick; tempRef = x; - ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + ref Octet d = ref Unsafe.Add(ref destBase, i); d.LoadFrom(ref temp); } } @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp /// internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte)); + VerifyHasVector8(nameof(BulkConvertNormalizedFloatToByte)); VerifySpanInput(source, dest, 8); if (source.Length == 0) @@ -186,17 +186,17 @@ namespace SixLabors.ImageSharp } ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); int n = source.Length / 8; var magick = new Vector(32768.0f); var scale = new Vector(255f) / new Vector(256f); // need to copy to a temporary struct, because - // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) + // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report - var temp = default(Octet.OfUInt32); - ref Vector tempRef = ref Unsafe.As>(ref temp); + var temp = default(Octet); + ref Vector tempRef = ref Unsafe.As, Vector>(ref temp); for (int i = 0; i < n; i++) { @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp x = (x * scale) + magick; tempRef = x; - ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + ref Octet d = ref Unsafe.Add(ref destBase, i); d.LoadFrom(ref temp); } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs index 7baa788e41..69d5dfa73a 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + ByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); source = source.Slice(adjustedCount); dest = dest.Slice(adjustedCount); @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows( + NormalizedFloatToByteSaturate( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -99,9 +99,9 @@ namespace SixLabors.ImageSharp } /// - /// Implementation , which is faster on new RyuJIT runtime. + /// Implementation , which is faster on new RyuJIT runtime. /// - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { VerifySpanInput(source, dest, Vector.Count); @@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of , which is faster on new .NET runtime. + /// Implementation of , which is faster on new .NET runtime. /// - internal static void BulkConvertNormalizedFloatToByteClampOverflows( + internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span dest) { diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs index 565ea08f5d..f16c91b40d 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,10 +19,10 @@ namespace SixLabors.ImageSharp public static class FallbackIntrinsics128 { /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat( + ByteToNormalizedFloat( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows( + NormalizedFloatToByteSaturate( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -67,10 +67,10 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of using . + /// Implementation of using . /// [MethodImpl(InliningOptions.ColdPath)] - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { VerifySpanInput(source, dest, 4); @@ -99,10 +99,10 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of using . + /// Implementation of using . /// [MethodImpl(InliningOptions.ColdPath)] - internal static void BulkConvertNormalizedFloatToByteClampOverflows( + internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span dest) { @@ -125,10 +125,7 @@ namespace SixLabors.ImageSharp Vector4 s = Unsafe.Add(ref sBase, i); s *= maxBytes; s += half; - - // I'm not sure if Vector4.Clamp() is properly implemented with intrinsics. - s = Vector4.Max(Vector4.Zero, s); - s = Vector4.Min(maxBytes, s); + s = Vector4Utilities.FastClamp(s, Vector4.Zero, maxBytes); ref ByteVector4 d = ref Unsafe.Add(ref dBase, i); d.X = (byte)s.X; @@ -148,4 +145,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 4c34e28bc8..0dc45d887b 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -15,9 +15,10 @@ namespace SixLabors.ImageSharp internal static partial class SimdUtils { /// - /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte. + /// Gets a value indicating whether code is being JIT-ed to AVX2 instructions + /// where both float and integer registers are of size 256 byte. /// - public static bool IsAvx2CompatibleArchitecture { get; } = + public static bool HasVector8 { get; } = Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; /// @@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Vector4 PseudoRound(this Vector4 v) { - var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1)); + var sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1)); return v + (sign * 0.5f); } @@ -60,16 +61,18 @@ namespace SixLabors.ImageSharp /// The source span of bytes /// The destination span of floats [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); #if SUPPORTS_EXTENDED_INTRINSICS - ExtendedIntrinsics.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + ExtendedIntrinsics.ByteToNormalizedFloatReduce(ref source, ref dest); #else - BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + BasicIntrinsics256.ByteToNormalizedFloatReduce(ref source, ref dest); #endif - FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.ByteToNormalizedFloatReduce(ref source, ref dest); // Deal with the remainder: if (source.Length > 0) @@ -87,16 +90,20 @@ namespace SixLabors.ImageSharp /// The source span of floats /// The destination span of bytes [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); -#if SUPPORTS_EXTENDED_INTRINSICS - ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); +#if SUPPORTS_RUNTIME_INTRINSICS + Avx2Intrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); +#elif SUPPORTS_EXTENDED_INTRINSICS + ExtendedIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); #else - BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + BasicIntrinsics256.NormalizedFloatToByteSaturateReduce(ref source, ref dest); #endif - FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.NormalizedFloatToByteSaturateReduce(ref source, ref dest); // Deal with the remainder: if (source.Length > 0) @@ -151,9 +158,9 @@ namespace SixLabors.ImageSharp private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f); [Conditional("DEBUG")] - private static void VerifyIsAvx2Compatible(string operation) + private static void VerifyHasVector8(string operation) { - if (!IsAvx2CompatibleArchitecture) + if (!HasVector8) { throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); } diff --git a/src/ImageSharp/Common/Helpers/TestHelpers.cs b/src/ImageSharp/Common/Helpers/TestHelpers.cs index d330233c4c..c6574e4b58 100644 --- a/src/ImageSharp/Common/Helpers/TestHelpers.cs +++ b/src/ImageSharp/Common/Helpers/TestHelpers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Common.Helpers @@ -13,14 +13,18 @@ namespace SixLabors.ImageSharp.Common.Helpers /// Only intended to be used in tests! /// internal const string ImageSharpBuiltAgainst = -#if NET472 - "netfx4.7.2"; +#if NETCOREAPP3_1 + "netcoreapp3.1"; #elif NETCOREAPP2_1 "netcoreapp2.1"; +#elif NETSTANDARD2_1 + "netstandard2.1"; +#elif NETSTANDARD2_0 + "netstandard2.0"; #elif NETSTANDARD1_3 "netstandard1.3"; #else - "netstandard2.0"; + "net472"; #endif } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/TolerantMath.cs b/src/ImageSharp/Common/Helpers/TolerantMath.cs index bef7eb1610..62b5644725 100644 --- a/src/ImageSharp/Common/Helpers/TolerantMath.cs +++ b/src/ImageSharp/Common/Helpers/TolerantMath.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -103,4 +103,4 @@ namespace SixLabors.ImageSharp return Math.Floor(a); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 75dbb032c5..9e43061702 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; @@ -86,9 +86,10 @@ namespace SixLabors.ImageSharp.Common.Helpers [MethodImpl(InliningOptions.ShortMethod)] public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) { - return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution) - ? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3 - : default; + IExifValue resolution = profile.GetValue(ExifTag.ResolutionUnit); + + // EXIF is 1, 2, 3 so we minus "1" off the result. + return resolution is null ? default : (PixelResolutionUnit)(byte)(resolution.Value - 1); } } } diff --git a/src/ImageSharp/Common/Helpers/Vector4Utilities.cs b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs new file mode 100644 index 0000000000..9fb4eb7909 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility methods for the struct. + /// + internal static class Vector4Utilities + { + /// + /// Restricts a vector between a minimum and a maximum value. + /// 5x Faster then . + /// + /// The vector to restrict. + /// The minimum value. + /// The maximum value. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 FastClamp(Vector4 x, Vector4 min, Vector4 max) + => Vector4.Min(Vector4.Max(x, min), max); + + /// + /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. + /// + /// The to premultiply + [MethodImpl(InliningOptions.ShortMethod)] + public static void Premultiply(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } + + /// + /// Reverses the result of premultiplying a vector via . + /// + /// The to premultiply + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnPremultiply(ref Vector4 source) + { + float w = source.W; + source /= w; + source.W = w; + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void Premultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Premultiply(ref v); + } + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnPremultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + UnPremultiply(ref v); + } + } + + /// + /// Transforms a vector by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Transform(ref Vector4 vector, ref ColorMatrix matrix) + { + float x = vector.X; + float y = vector.Y; + float z = vector.Z; + float w = vector.W; + + vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51; + vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52; + vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53; + vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54; + } + + /// + /// Bulk variant of . + /// + /// The span of vectors + /// The transformation matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Transform(Span vectors, ref ColorMatrix matrix) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Transform(ref v, ref matrix); + } + } + } +} diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs deleted file mode 100644 index a4e0921d0a..0000000000 --- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp -{ - /// - /// Utility methods for the struct. - /// - internal static class Vector4Utils - { - /// - /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. - /// - /// The to premultiply - [MethodImpl(InliningOptions.ShortMethod)] - public static void Premultiply(ref Vector4 source) - { - float w = source.W; - source *= w; - source.W = w; - } - - /// - /// Reverses the result of premultiplying a vector via . - /// - /// The to premultiply - [MethodImpl(InliningOptions.ShortMethod)] - public static void UnPremultiply(ref Vector4 source) - { - float w = source.W; - source /= w; - source.W = w; - } - - /// - /// Bulk variant of - /// - /// The span of vectors - [MethodImpl(InliningOptions.ShortMethod)] - public static void Premultiply(Span vectors) - { - // TODO: This method can be AVX2 optimized using Vector - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Premultiply(ref v); - } - } - - /// - /// Bulk variant of - /// - /// The span of vectors - [MethodImpl(InliningOptions.ShortMethod)] - public static void UnPremultiply(Span vectors) - { - // TODO: This method can be AVX2 optimized using Vector - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - UnPremultiply(ref v); - } - } - - /// - /// Transforms a vector by the given matrix. - /// - /// The source vector. - /// The transformation matrix. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Transform(ref Vector4 vector, ref ColorMatrix matrix) - { - float x = vector.X; - float y = vector.Y; - float z = vector.Z; - float w = vector.W; - - vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51; - vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52; - vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53; - vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54; - } - - /// - /// Bulk variant of . - /// - /// The span of vectors - /// The transformation matrix. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Transform(Span vectors, ref ColorMatrix matrix) - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Transform(ref v, ref matrix); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs deleted file mode 100644 index 40163bc789..0000000000 --- a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Threading.Tasks; - -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.ParallelUtils -{ - /// - /// Defines execution settings for methods in . - /// - internal readonly struct ParallelExecutionSettings - { - /// - /// Default value for . - /// - public const int DefaultMinimumPixelsProcessedPerTask = 4096; - - /// - /// Initializes a new instance of the struct. - /// - public ParallelExecutionSettings( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - MemoryAllocator memoryAllocator) - { - this.MaxDegreeOfParallelism = maxDegreeOfParallelism; - this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; - this.MemoryAllocator = memoryAllocator; - } - - /// - /// Initializes a new instance of the struct. - /// - public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) - : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) - { - } - - /// - /// Gets the MemoryAllocator - /// - public MemoryAllocator MemoryAllocator { get; } - - /// - /// Gets the value used for initializing when using TPL. - /// - public int MaxDegreeOfParallelism { get; } - - /// - /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL. - /// Launching tasks for pixel regions below this limit is not worth the overhead. - /// Initialized with by default, - /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.) - /// - public int MinimumPixelsProcessedPerTask { get; } - - /// - /// Creates a new instance of - /// having multiplied by - /// - public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) - { - return new ParallelExecutionSettings( - this.MaxDegreeOfParallelism, - this.MinimumPixelsProcessedPerTask * multiplier, - this.MemoryAllocator); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs deleted file mode 100644 index 1e7299720d..0000000000 --- a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.ParallelUtils -{ - /// - /// Utility methods for batched processing of pixel row intervals. - /// Parallel execution is optimized for image processing. - /// Use this instead of direct calls! - /// - internal static class ParallelHelper - { - /// - /// Get the default for a - /// - public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration) - { - return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) - { - ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings(); - - IterateRows(rectangle, parallelSettings, body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) - { - ValidateRectangle(rectangle); - - int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); - - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); - body(rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - i => - { - int yMin = rectangle.Top + (i * verticalStep); - int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); - - var rows = new RowInterval(yMin, yMax); - body(rows); - }); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - public static void IterateRowsWithTempBuffer( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action> body) - where T : unmanaged - { - ValidateRectangle(rectangle); - - int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); - - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator; - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); - using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) - { - body(rows, buffer.Memory); - } - - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - i => - { - int yMin = rectangle.Top + (i * verticalStep); - int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); - - var rows = new RowInterval(yMin, yMax); - - using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) - { - body(rows, buffer.Memory); - } - }); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - public static void IterateRowsWithTempBuffer( - Rectangle rectangle, - Configuration configuration, - Action> body) - where T : unmanaged - { - IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); - - private static void ValidateRectangle(Rectangle rectangle) - { - Guard.MustBeGreaterThan( - rectangle.Width, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); - - Guard.MustBeGreaterThan( - rectangle.Height, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); - } - } -} diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs deleted file mode 100644 index 7ece2ed562..0000000000 --- a/src/ImageSharp/Common/Tuples/Octet.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tuples -{ - /// - /// Contains 8 element value tuples of various types. - /// - internal static class Octet - { - /// - /// Value tuple of -s. - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] - public struct OfUInt32 - { - [FieldOffset(0 * sizeof(uint))] - public uint V0; - - [FieldOffset(1 * sizeof(uint))] - public uint V1; - - [FieldOffset(2 * sizeof(uint))] - public uint V2; - - [FieldOffset(3 * sizeof(uint))] - public uint V3; - - [FieldOffset(4 * sizeof(uint))] - public uint V4; - - [FieldOffset(5 * sizeof(uint))] - public uint V5; - - [FieldOffset(6 * sizeof(uint))] - public uint V6; - - [FieldOffset(7 * sizeof(uint))] - public uint V7; - - public override string ToString() - { - return $"{nameof(Octet)}.{nameof(OfUInt32)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(ref OfByte src) - { - this.V0 = src.V0; - this.V1 = src.V1; - this.V2 = src.V2; - this.V3 = src.V3; - this.V4 = src.V4; - this.V5 = src.V5; - this.V6 = src.V6; - this.V7 = src.V7; - } - } - - /// - /// Value tuple of -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8)] - public struct OfByte - { - [FieldOffset(0)] - public byte V0; - - [FieldOffset(1)] - public byte V1; - - [FieldOffset(2)] - public byte V2; - - [FieldOffset(3)] - public byte V3; - - [FieldOffset(4)] - public byte V4; - - [FieldOffset(5)] - public byte V5; - - [FieldOffset(6)] - public byte V6; - - [FieldOffset(7)] - public byte V7; - - public override string ToString() - { - return $"{nameof(Octet)}.{nameof(OfByte)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(ref OfUInt32 src) - { - this.V0 = (byte)src.V0; - this.V1 = (byte)src.V1; - this.V2 = (byte)src.V2; - this.V3 = (byte)src.V3; - this.V4 = (byte)src.V4; - this.V5 = (byte)src.V5; - this.V6 = (byte)src.V6; - this.V7 = (byte)src.V7; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs new file mode 100644 index 0000000000..71e7da801e --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Octet{T}.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Tuples +{ + /// + /// Contains 8 element value tuples of various types. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Octet + where T : unmanaged + { + public T V0; + public T V1; + public T V2; + public T V3; + public T V4; + public T V5; + public T V6; + public T V7; + + /// + public override readonly string ToString() + { + return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; + } + } + + /// + /// Extension methods for the type. + /// + internal static class OctetExtensions + { + /// + /// Loads the fields in a target of from one of type. + /// + /// The target of instance. + /// The source of instance. + [MethodImpl(InliningOptions.ShortMethod)] + public static void LoadFrom(ref this Octet destination, ref Octet source) + { + destination.V0 = source.V0; + destination.V1 = source.V1; + destination.V2 = source.V2; + destination.V3 = source.V3; + destination.V4 = source.V4; + destination.V5 = source.V5; + destination.V6 = source.V6; + destination.V7 = source.V7; + } + + /// + /// Loads the fields in a target of from one of type. + /// + /// The target of instance. + /// The source of instance. + [MethodImpl(InliningOptions.ShortMethod)] + public static void LoadFrom(ref this Octet destination, ref Octet source) + { + destination.V0 = (byte)source.V0; + destination.V1 = (byte)source.V1; + destination.V2 = (byte)source.V2; + destination.V3 = (byte)source.V3; + destination.V4 = (byte)source.V4; + destination.V5 = (byte)source.V5; + destination.V6 = (byte)source.V6; + destination.V7 = (byte)source.V7; + } + } +} diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs index b3a32deeef..1fdae0d5dd 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tuples /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscalePreAvx2(float downscaleFactor) + internal void RoundAndDownscalePreVector8(float downscaleFactor) { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tuples /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleAvx2(float downscaleFactor) + internal void RoundAndDownscaleVector8(float downscaleFactor) { ref Vector self = ref Unsafe.As>(ref this); Vector v = self; @@ -79,4 +79,4 @@ namespace SixLabors.ImageSharp.Tuples return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 0d44db8d87..17ac8c3fd8 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -3,19 +3,21 @@ using System; using System.Collections.Generic; +using System.Net.Http; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; -using SixLabors.Memory; namespace SixLabors.ImageSharp { /// - /// Provides configuration code which allows altering default behaviour or extending the library. + /// Provides configuration which allows altering default behaviour or extending the library. /// public sealed class Configuration { @@ -63,7 +65,7 @@ namespace SixLabors.ImageSharp get => this.maxDegreeOfParallelism; set { - if (value <= 0) + if (value == 0 || value < -1) { throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); } @@ -72,6 +74,12 @@ namespace SixLabors.ImageSharp } } + /// + /// Gets a set of properties for the Congiguration. + /// + /// This can be used for storing global settings and defaults to be accessable to processors. + public IDictionary Properties { get; } = new Dictionary(); + /// /// Gets the currently registered s. /// @@ -107,7 +115,8 @@ namespace SixLabors.ImageSharp /// The default value is 1MB. /// /// - /// Currently only used by Resize. + /// Currently only used by Resize. If the working buffer is expected to be discontiguous, + /// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used. /// internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; @@ -150,6 +159,7 @@ namespace SixLabors.ImageSharp /// /// /// . + /// . /// /// The default configuration of . internal static Configuration CreateDefaultInstance() @@ -158,7 +168,8 @@ namespace SixLabors.ImageSharp new PngConfigurationModule(), new JpegConfigurationModule(), new GifConfigurationModule(), - new BmpConfigurationModule()); + new BmpConfigurationModule(), + new TgaConfigurationModule()); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index a404ab418b..cafe101060 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -28,11 +29,22 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Decode(stream); + var decoder = new BmpDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + throw new InvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index b7733e0269..eeb1ae714a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -11,7 +11,6 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Bmp { @@ -115,6 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.options = options; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); + /// /// Decodes the image from the specified this._stream and sets /// the data to image. @@ -127,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { try { @@ -252,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The output pixel buffer containing the decoded image. /// Whether the bitmap is inverted. private void ReadBitFields(Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.infoHeader.BitsPerPixel == 16) { @@ -292,27 +296,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRle(BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; - using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) - using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; - if (compression == BmpCompression.RLE8) + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; + Span bufferSpan = buffer.Memory.Span; + if (compression is BmpCompression.RLE8) { - this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } else { - this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span bufferRow = buffer.GetRowSpan(y); + int rowStartIdx = y * width; + Span bufferRow = bufferSpan.Slice(rowStartIdx, width); Span pixelRow = pixels.GetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; @@ -322,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int x = 0; x < width; x++) { byte colorIdx = bufferRow[x]; - if (undefinedPixels[x, y]) + if (undefinedPixelsSpan[rowStartIdx + x]) { switch (this.options.RleSkippedPixelHandling) { @@ -369,16 +376,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRle24(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean)) - using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; Span bufferSpan = buffer.GetSpan(); - this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + + this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); @@ -387,10 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp if (rowHasUndefinedPixels) { // Slow path with undefined pixels. + var yMulWidth = y * width; + int rowStartIdx = yMulWidth * 3; for (int x = 0; x < width; x++) { - int idx = (y * width * 3) + (x * 3); - if (undefinedPixels[x, y]) + int idx = rowStartIdx + (x * 3); + if (undefinedPixelsSpan[yMulWidth + x]) { switch (this.options.RleSkippedPixelHandling) { @@ -418,9 +429,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp else { // Fast path without any undefined pixels. + int rowStartIdx = y * width * 3; for (int x = 0; x < width; x++) { - int idx = (y * width * 3) + (x * 3); + int idx = rowStartIdx + (x * 3); color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); pixelRow[x] = color; } @@ -443,18 +455,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Keeps track of rows, which have undefined pixels. private void UncompressRle4(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) { -#if NETCOREAPP2_1 Span cmd = stackalloc byte[2]; -#else - var cmd = new byte[2]; -#endif int count = 0; while (count < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); } if (cmd[0] == RleCommand) @@ -554,18 +562,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Keeps track of rows, which have undefined pixels. private void UncompressRle8(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) { -#if NETCOREAPP2_1 Span cmd = stackalloc byte[2]; -#else - var cmd = new byte[2]; -#endif int count = 0; while (count < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); } if (cmd[0] == RleCommand) @@ -637,18 +641,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Keeps track of rows, which have undefined pixels. private void UncompressRle24(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) { -#if NETCOREAPP2_1 Span cmd = stackalloc byte[2]; -#else - var cmd = new byte[2]; -#endif int uncompressedPixels = 0; while (uncompressedPixels < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap."); + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap."); } if (cmd[0] == RleCommand) @@ -814,7 +814,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// the bytes per color palette entry's can be 3 bytes instead of 4. /// Whether the bitmap is inverted. private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Pixels per byte (bits per pixel). int ppb = 8 / bitsPerPixel; @@ -872,7 +872,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The bitmask for the green channel. /// The bitmask for the blue channel. private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 2); int stride = (width * 2) + padding; @@ -939,7 +939,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 3); @@ -968,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); @@ -998,7 +998,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); @@ -1099,7 +1099,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The bitmask for the blue channel. /// The bitmask for the alpha channel. private void ReadRgb32BitFields(Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; int padding = CalculatePadding(width, 4); @@ -1211,11 +1211,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private void ReadInfoHeader() { -#if NETCOREAPP2_1 Span buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; -#else - var buffer = new byte[BmpInfoHeader.MaxHeaderSize]; -#endif // Read the header size. this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); @@ -1319,7 +1315,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.metadata = meta; short bitsPerPixel = this.infoHeader.BitsPerPixel; - this.bmpMetadata = this.metadata.GetFormatMetadata(BmpFormat.Instance); + this.bmpMetadata = this.metadata.GetBmpMetadata(); this.bmpMetadata.InfoHeaderType = infoHeaderType; // We can only encode at these bit rates so far (1 bit and 4 bit are still missing). @@ -1337,11 +1333,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private void ReadFileHeader() { -#if NETCOREAPP2_1 Span buffer = stackalloc byte[BmpFileHeader.Size]; -#else - var buffer = new byte[BmpFileHeader.Size]; -#endif this.stream.Read(buffer, 0, BmpFileHeader.Size); short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); @@ -1439,7 +1431,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp // Make sure, that we will not read pass the bitmap offset (starting position of image data). if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) { - BmpThrowHelper.ThrowImageFormatException( + BmpThrowHelper.ThrowInvalidImageContentException( $"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset."); } @@ -1453,7 +1445,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int skipAmount = this.fileHeader.Offset - (int)this.stream.Position; if ((skipAmount + (int)this.stream.Position) > this.stream.Length) { - BmpThrowHelper.ThrowImageFormatException("Invalid fileheader offset found. Offset is greater than the stream length."); + BmpThrowHelper.ThrowInvalidImageContentException("Invalid fileheader offset found. Offset is greater than the stream length."); } if (skipAmount > 0) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 612675c33c..9c05ae2d50 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index a5e1ee5dbc..7d27995038 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -11,8 +11,8 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Bmp { @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; this.writeV4Header = options.SupportTransparency; - this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256); + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; } /// @@ -98,14 +98,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; - BmpMetadata bmpMetadata = metadata.GetFormatMetadata(BmpFormat.Instance); + BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; short bpp = (short)this.bitsPerPixel; @@ -173,11 +173,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp reserved: 0, offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); -#if NETCOREAPP2_1 Span buffer = stackalloc byte[infoHeaderSize]; -#else - var buffer = new byte[infoHeaderSize]; -#endif fileHeader.WriteTo(buffer); stream.Write(buffer, 0, BmpFileHeader.Size); @@ -207,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// private void WriteImage(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Buffer2D pixels = image.PixelBuffer; switch (this.bitsPerPixel) @@ -239,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write32Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { @@ -263,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write24Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { @@ -287,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write16Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) { @@ -313,13 +309,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write8Bit(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - bool isGray8 = typeof(TPixel) == typeof(Gray8); + bool isL8 = typeof(TPixel) == typeof(L8); using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) { Span colorPalette = colorPaletteBuffer.GetSpan(); - if (isGray8) + if (isL8) { this.Write8BitGray(stream, image, colorPalette); } @@ -338,38 +334,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// A byte span of size 1024 for the color palette. private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); + + ReadOnlySpan quantizedColors = quantized.Palette.Span; + var color = default(Rgba32); + + // TODO: Use bulk conversion here for better perf + int idx = 0; + foreach (TPixel quantizedColor in quantizedColors) { - ReadOnlySpan quantizedColors = quantized.Palette.Span; - var color = default(Rgba32); + quantizedColor.ToRgba32(ref color); + colorPalette[idx] = color.B; + colorPalette[idx + 1] = color.G; + colorPalette[idx + 2] = color.R; - // TODO: Use bulk conversion here for better perf - int idx = 0; - foreach (TPixel quantizedColor in quantizedColors) - { - quantizedColor.ToRgba32(ref color); - colorPalette[idx] = color.B; - colorPalette[idx + 1] = color.G; - colorPalette[idx + 2] = color.R; - - // Padding byte, always 0. - colorPalette[idx + 3] = 0; - idx += 4; - } + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + idx += 4; + } + + stream.Write(colorPalette); - stream.Write(colorPalette); + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); + stream.Write(pixelSpan); - for (int y = image.Height - 1; y >= 0; y--) + for (int i = 0; i < this.padding; i++) { - ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); - stream.Write(pixelSpan); - - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } + stream.WriteByte(0); } } } @@ -382,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// A byte span of size 1024 for the color palette. private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Create a color palette with 256 different gray values. for (int i = 0; i <= 255; i++) diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index 3d7510bc2a..e1fc9ef1fe 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -22,9 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp private bool IsSupportedFileFormat(ReadOnlySpan header) { - short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); - return header.Length >= this.HeaderSize && - (fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray); + if (header.Length >= this.HeaderSize) + { + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); + return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; + } + + return false; } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index 9ede660705..3411de0421 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -393,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp break; default: // Compression type 3 (1DHuffman) is not supported. - BmpThrowHelper.ThrowImageFormatException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); + BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); break; } diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs similarity index 100% rename from src/ImageSharp/Formats/Bmp/BmpMetaData.cs rename to src/ImageSharp/Formats/Bmp/BmpMetadata.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs index 443471404e..c48566f835 100644 --- a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs +++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp internal static class BmpThrowHelper { /// - /// Cold path optimization for throwing -s + /// Cold path optimization for throwing 's /// /// The error message for the exception. [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowImageFormatException(string errorMessage) - { - throw new ImageFormatException(errorMessage); - } + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); /// - /// Cold path optimization for throwing -s + /// Cold path optimization for throwing 's /// /// The error message for the exception. [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowNotSupportedException(string errorMessage) - { - throw new NotSupportedException(errorMessage); - } + => throw new NotSupportedException(errorMessage); } } diff --git a/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs new file mode 100644 index 0000000000..0315b3c768 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the bmp format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static BmpMetadata GetBmpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(BmpFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index c9d631da0e..06c4b3fc6e 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; @@ -21,11 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const string FileVersion = "89a"; - /// - /// The ASCII encoded bytes used to identify the GIF file. - /// - internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion); - /// /// The extension block introducer !. /// @@ -51,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; - /// - /// The ASCII encoded application identification bytes. - /// - internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification); - /// /// The Netscape looping application sub block size. /// @@ -110,5 +101,25 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The collection of file extensions that equate to a Gif. /// public static readonly IEnumerable FileExtensions = new[] { "gif" }; + + /// + /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ). + /// + internal static ReadOnlySpan MagicNumber => new[] + { + (byte)'G', (byte)'I', (byte)'F', + (byte)'8', (byte)'9', (byte)'a' + }; + + /// + /// Gets the ASCII encoded application identification bytes (representing ). + /// + internal static ReadOnlySpan NetscapeApplicationIdentificationBytes => new[] + { + (byte)'N', (byte)'E', (byte)'T', + (byte)'S', (byte)'C', (byte)'A', + (byte)'P', (byte)'E', + (byte)'2', (byte)'.', (byte)'0' + }; } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 7691ec1aa5..553163bd7e 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -24,10 +26,23 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(stream); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + + // Not reachable, as the previous statement will throw a exception. + return null; + } } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index c11e93a93a..e0d8b3cd96 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -6,12 +6,10 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Advanced; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Gif { @@ -88,20 +86,25 @@ namespace SixLabors.ImageSharp.Formats.Gif public bool IgnoreMetadata { get; internal set; } /// - /// Gets the decoding mode for multi-frame images + /// Gets the decoding mode for multi-frame images. /// public FrameDecodingMode DecodingMode { get; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height); + private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator; /// /// Decodes the stream to the image. /// /// The pixel format. - /// The stream containing image data. + /// The stream containing image data. /// The decoded image public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image image = null; ImageFrame previousFrame = null; @@ -238,6 +241,10 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream.Read(this.buffer, 0, 9); this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); + if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) + { + GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); + } } /// @@ -276,9 +283,8 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Could be XMP or something else not supported yet. - // Back up and skip. - this.stream.Position -= appLength + 1; - this.SkipBlock(appLength); + // Skip the subblock and terminator. + this.SkipBlock(subBlockSize); return; } @@ -316,7 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (length > GifConstants.MaxCommentSubBlockLength) { - throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); + GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); } if (this.IgnoreMetadata) @@ -346,12 +352,12 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The image to decode the information to. /// The previous frame. private void ReadFrame(ref Image image, ref ImageFrame previousFrame) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.ReadImageDescriptor(); IManagedByteBuffer localColorTable = null; - IManagedByteBuffer indices = null; + Buffer2D indices = null; try { // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. @@ -362,11 +368,11 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream.Read(localColorTable.Array, 0, length); } - indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean); + indices = this.configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); - this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan()); + this.ReadFrameIndices(indices); ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan()); - this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor); + this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); // Skip any remaining blocks this.SkipBlock(); @@ -381,16 +387,13 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Reads the frame indices marking the color to use for each pixel. /// - /// The . - /// The pixel array to write to. + /// The 2D pixel buffer to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span indices) + private void ReadFrameIndices(Buffer2D indices) { int dataSize = this.stream.ReadByte(); - using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream)) - { - lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); - } + using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream); + lzwDecoder.DecodePixels(dataSize, indices); } /// @@ -402,10 +405,9 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The indexed pixels. /// The color table containing the available colors. /// The - private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Span indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) - where TPixel : struct, IPixel + private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Buffer2D indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) + where TPixel : unmanaged, IPixel { - ref byte indicesRef = ref MemoryMarshal.GetReference(indices); int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; @@ -438,13 +440,20 @@ namespace SixLabors.ImageSharp.Formats.Gif this.RestoreToBackground(imageFrame); } - int i = 0; int interlacePass = 0; // The interlace pass int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line - - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + int descriptorTop = descriptor.Top; + int descriptorBottom = descriptorTop + descriptor.Height; + int descriptorLeft = descriptor.Left; + int descriptorRight = descriptorLeft + descriptor.Width; + bool transFlag = this.graphicsControlExtension.TransparencyFlag; + byte transIndex = this.graphicsControlExtension.TransparencyIndex; + + for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) { + ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop)); + // Check if this image is interlaced. int writeY; // the target y offset to write to if (descriptor.InterlaceFlag) @@ -480,35 +489,29 @@ namespace SixLabors.ImageSharp.Formats.Gif } ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY)); - bool transFlag = this.graphicsControlExtension.TransparencyFlag; if (!transFlag) { // #403 The left + width value can be larger than the image width - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++) + for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - int index = Unsafe.Add(ref indicesRef, i); + int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); Rgb24 rgb = colorTable[index]; pixel.FromRgb24(rgb); - - i++; } } else { - byte transIndex = this.graphicsControlExtension.TransparencyIndex; - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++) + for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - int index = Unsafe.Add(ref indicesRef, i); + int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); if (transIndex != index) { ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); Rgb24 rgb = colorTable[index]; pixel.FromRgb24(rgb); } - - i++; } } } @@ -533,7 +536,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The frame. private void RestoreToBackground(ImageFrame frame) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.restoreArea is null) { @@ -553,7 +556,7 @@ namespace SixLabors.ImageSharp.Formats.Gif [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetFrameMetadata(ImageFrameMetadata meta) { - GifFrameMetadata gifMeta = meta.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata gifMeta = meta.GetGifMetadata(); if (this.graphicsControlExtension.DelayTime > 0) { gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; @@ -615,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } this.metadata = meta; - this.gifMetadata = meta.GetFormatMetadata(GifFormat.Instance); + this.gifMetadata = meta.GetGifMetadata(); this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag ? GifColorTableMode.Global : GifColorTableMode.Local; diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index fef311596e..53c4c6f3fd 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Gif @@ -17,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Gets or sets the quantizer for reducing the color count. /// Defaults to the /// - public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); + public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree; /// /// Gets or sets the color table mode: Global or local. @@ -26,9 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var encoder = new GifEncoderCore(image.GetConfiguration().MemoryAllocator, this); + var encoder = new GifEncoderCore(image.GetConfiguration(), this); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index c8fc448125..62410025c6 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -6,13 +6,11 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Gif { @@ -29,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Configuration bound to the encoding operation. /// - private Configuration configuration; + private readonly Configuration configuration; /// /// A reusable buffer used to reduce allocations. @@ -54,11 +52,12 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// - /// The to use for buffer allocations. + /// The configuration which allows altering default behaviour or extending the library. /// The options for the encoder. - public GifEncoderCore(MemoryAllocator memoryAllocator, IGifEncoderOptions options) + public GifEncoderCore(Configuration configuration, IGifEncoderOptions options) { - this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; this.quantizer = options.Quantizer; this.colorTableMode = options.ColorTableMode; } @@ -70,27 +69,25 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.configuration = image.GetConfiguration(); - ImageMetadata metadata = image.Metadata; - GifMetadata gifMetadata = metadata.GetFormatMetadata(GifFormat.Instance); - this.colorTableMode = this.colorTableMode ?? gifMetadata.ColorTableMode; + GifMetadata gifMetadata = metadata.GetGifMetadata(); + this.colorTableMode ??= gifMetadata.ColorTableMode; bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; // Quantize the image returning a palette. - IQuantizedFrame quantized; - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) + IndexedImageFrame quantized; + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) { - quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); // Write the header. this.WriteHeader(stream); @@ -123,20 +120,25 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Clean up. - quantized?.Dispose(); + quantized.Dispose(); // TODO: Write extension etc stream.WriteByte(GifConstants.EndIntroducer); } - private void EncodeGlobal(Image image, IQuantizedFrame quantized, int transparencyIndex, Stream stream) - where TPixel : struct, IPixel + private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream) + where TPixel : unmanaged, IPixel { + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + EuclideanPixelMap pixelMap = default; + bool pixelMapSet = false; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; - GifFrameMetadata frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); this.WriteImageDescriptor(frame, false, stream); @@ -146,54 +148,59 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (IFrameQuantizer paletteFrameQuantizer = - new PaletteFrameQuantizer(this.quantizer.Diffuser, quantized.Palette)) + if (!pixelMapSet) { - using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame)) - { - this.WriteImageData(paletteQuantized, stream); - } + pixelMapSet = true; + pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); } + + using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, pixelMap); + using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.WriteImageData(paletteQuantized, stream); } } } - private void EncodeLocal(Image image, IQuantizedFrame quantized, Stream stream) - where TPixel : struct, IPixel + private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel { ImageFrame previousFrame = null; GifFrameMetadata previousMeta = null; - foreach (ImageFrame frame in image.Frames) + for (int i = 0; i < image.Frames.Count; i++) { + ImageFrame frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; - GifFrameMetadata frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); if (quantized is null) { // Allow each frame to be encoded at whatever color depth the frame designates if set. if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength && frameMetadata.ColorTableLength > 0) { - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration(), frameMetadata.ColorTableLength)) + var options = new QuantizerOptions { - quantized = frameQuantizer.QuantizeFrame(frame); - } + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale, + MaxColors = frameMetadata.ColorTableLength + }; + + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, options); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } else { - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) - { - quantized = frameQuantizer.QuantizeFrame(frame); - } + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); - quantized?.Dispose(); + quantized.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; previousMeta = frameMetadata; @@ -203,32 +210,28 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Returns the index of the most transparent color in the palette. /// - /// - /// The quantized. - /// + /// The quantized frame. /// The pixel format. /// /// The . /// - private int GetTransparentIndex(IQuantizedFrame quantized) - where TPixel : struct, IPixel + private int GetTransparentIndex(IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { - // Transparent pixels are much more likely to be found at the end of a palette + // Transparent pixels are much more likely to be found at the end of a palette. int index = -1; - int length = quantized.Palette.Length; + ReadOnlySpan paletteSpan = quantized.Palette.Span; - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(length)) - { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteSpan.Length); + Span rgbaSpan = rgbaOwner.GetSpan(); + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan); + ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan); - for (int i = quantized.Palette.Length - 1; i >= 0; i--) + for (int i = rgbaSpan.Length - 1; i >= 0; i--) + { + if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default)) { - if (Unsafe.Add(ref paletteRef, i).Equals(default)) - { - index = i; - } + index = i; } } @@ -240,7 +243,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); + private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); /// /// Writes the logical screen descriptor to the stream. @@ -328,8 +331,9 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - foreach (string comment in metadata.Comments) + for (var i = 0; i < metadata.Comments.Count; i++) { + string comment = metadata.Comments[i]; this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = GifConstants.CommentLabel; stream.Write(this.buffer, 0, 2); @@ -337,7 +341,9 @@ namespace SixLabors.ImageSharp.Formats.Gif // Comment will be stored in chunks of 255 bytes, if it exceeds this size. ReadOnlySpan commentSpan = comment.AsSpan(); int idx = 0; - for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength) + for (; + idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; + idx += GifConstants.MaxCommentSubBlockLength) { WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); } @@ -393,7 +399,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The extension to write to the stream. /// The stream to write to. - public void WriteExtension(IGifExtension extension, Stream stream) + private void WriteExtension(TGifExtension extension, Stream stream) + where TGifExtension : struct, IGifExtension { this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = extension.Label; @@ -413,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Whether to use the global color table. /// The stream to write to. private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { byte packedValue = GifImageDescriptor.GetPackedValue( localColorTableFlag: hasColorTable, @@ -439,37 +446,33 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(IQuantizedFrame image, Stream stream) - where TPixel : struct, IPixel + private void WriteColorTable(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel { // The maximum number of colors for the bit depth - int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; - int pixelCount = image.Palette.Length; + int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - { - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - image.Palette.Span, - colorTable.GetSpan(), - pixelCount); - stream.Write(colorTable.Array, 0, colorTableLength); - } + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + image.Palette.Span, + colorTable.GetSpan(), + image.Palette.Length); + + stream.Write(colorTable.Array, 0, colorTableLength); } /// /// Writes the image pixel data to the stream. /// /// The pixel format. - /// The containing indexed pixels. + /// The containing indexed pixels. /// The stream to write to. - private void WriteImageData(IQuantizedFrame image, Stream stream) - where TPixel : struct, IPixel + private void WriteImageData(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel { - using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) - { - encoder.Encode(image.GetPixelSpan(), stream); - } + using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); + encoder.Encode(((IPixelSource)image).PixelBuffer, stream); } } } diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs similarity index 100% rename from src/ImageSharp/Formats/Gif/GifFrameMetaData.cs rename to src/ImageSharp/Formats/Gif/GifFrameMetadata.cs diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs deleted file mode 100644 index b00db6752b..0000000000 --- a/src/ImageSharp/Formats/Gif/GifMetaData.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; - -namespace SixLabors.ImageSharp.Formats.Gif -{ - /// - /// Provides Gif specific metadata information for the image. - /// - public class GifMetadata : IDeepCloneable - { - /// - /// Initializes a new instance of the class. - /// - public GifMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private GifMetadata(GifMetadata other) - { - this.RepeatCount = other.RepeatCount; - this.ColorTableMode = other.ColorTableMode; - this.GlobalColorTableLength = other.GlobalColorTableLength; - - for (int i = 0; i < other.Comments.Count; i++) - { - this.Comments.Add(other.Comments[i]); - } - } - - /// - /// Gets or sets the number of times any animation is repeated. - /// - /// 0 means to repeat indefinitely, count is set as play n + 1 times - /// - /// - public ushort RepeatCount { get; set; } - - /// - /// Gets or sets the color table mode. - /// - public GifColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the length of the global color table if present. - /// - public int GlobalColorTableLength { get; set; } - - /// - /// Gets or sets the the collection of comments about the graphics, credits, descriptions or any - /// other type of non-control and non-graphic data. - /// - public IList Comments { get; set; } = new List(); - - /// - public IDeepCloneable DeepClone() => new GifMetadata(this); - } -} diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs new file mode 100644 index 0000000000..5fe86c4dd3 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides Gif specific metadata information for the image. + /// + public class GifMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public GifMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifMetadata(GifMetadata other) + { + this.RepeatCount = other.RepeatCount; + this.ColorTableMode = other.ColorTableMode; + this.GlobalColorTableLength = other.GlobalColorTableLength; + + for (int i = 0; i < other.Comments.Count; i++) + { + this.Comments.Add(other.Comments[i]); + } + } + + /// + /// Gets or sets the number of times any animation is repeated. + /// + /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. + /// + /// + public ushort RepeatCount { get; set; } = 1; + + /// + /// Gets or sets the color table mode. + /// + public GifColorTableMode ColorTableMode { get; set; } + + /// + /// Gets or sets the length of the global color table if present. + /// + public int GlobalColorTableLength { get; set; } + + /// + /// Gets or sets the the collection of comments about the graphics, credits, descriptions or any + /// other type of non-control and non-graphic data. + /// + public IList Comments { get; set; } = new List(); + + /// + public IDeepCloneable DeepClone() => new GifMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs new file mode 100644 index 0000000000..1b20c9f64c --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + internal static class GifThrowHelper + { + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); + + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); + } +} diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index af390e9545..8289ee75b0 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -8,7 +8,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Gif { @@ -66,15 +65,15 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Decodes and decompresses all pixel indices from the stream. /// - /// The width of the pixel index array. - /// The height of the pixel index array. /// Size of the data. /// The pixel array to decode to. - public void DecodePixels(int width, int height, int dataSize, Span pixels) + public void DecodePixels(int dataSize, Buffer2D pixels) { Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); // The resulting index table length. + int width = pixels.Width; + int height = pixels.Height; int length = width * height; // Calculate the clear code. The value of the clear code is 2 ^ dataSize @@ -106,21 +105,28 @@ namespace SixLabors.ImageSharp.Formats.Gif ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); - ref byte pixelsRef = ref MemoryMarshal.GetReference(pixels); for (code = 0; code < clearCode; code++) { Unsafe.Add(ref suffixRef, code) = (byte)code; } -#if NETCOREAPP2_1 - Span buffer = stackalloc byte[255]; -#else - var buffer = new byte[255]; -#endif + Span buffer = stackalloc byte[byte.MaxValue]; + int y = 0; + int x = 0; + int rowMax = width; + ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y)); while (xyz < length) { + // Reset row reference. + if (xyz == rowMax) + { + x = 0; + pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y)); + rowMax = (y * width) + width; + } + if (top == 0) { if (bits < codeSize) @@ -214,7 +220,8 @@ namespace SixLabors.ImageSharp.Formats.Gif top--; // Clear missing pixels - Unsafe.Add(ref pixelsRef, xyz++) = (byte)Unsafe.Add(ref pixelStackRef, top); + xyz++; + Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, top); } } @@ -227,11 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] -#if NETCOREAPP2_1 private int ReadBlock(Span buffer) -#else - private int ReadBlock(byte[] buffer) -#endif { int bufferSize = this.stream.ReadByte(); diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index d809106143..516b82396d 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Gif { @@ -42,13 +41,33 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private const int HashSize = 5003; + /// + /// The amount to shift each code. + /// + private const int HashShift = 4; + /// /// Mask used when shifting pixel values /// private static readonly int[] Masks = { - 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, - 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + 0b0, + 0b1, + 0b11, + 0b111, + 0b1111, + 0b11111, + 0b111111, + 0b1111111, + 0b11111111, + 0b111111111, + 0b1111111111, + 0b11111111111, + 0b111111111111, + 0b1111111111111, + 0b11111111111111, + 0b111111111111111, + 0b1111111111111111 }; /// @@ -81,16 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private readonly byte[] accumulators = new byte[256]; - /// - /// For dynamic table sizing - /// - private readonly int hsize = HashSize; - - /// - /// The current position within the pixelArray. - /// - private int position; - /// /// Number of bits/code /// @@ -178,15 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Encodes and compresses the indexed pixels to the stream. /// - /// The span of indexed pixels. + /// The 2D buffer of indexed pixels. /// The stream to write to. - public void Encode(ReadOnlySpan indexedPixels, Stream stream) + public void Encode(Buffer2D indexedPixels, Stream stream) { // Write "initial code size" byte stream.WriteByte((byte)this.initialCodeSize); - this.position = 0; - // Compress and write the pixel data this.Compress(indexedPixels, this.initialCodeSize + 1, stream); @@ -200,10 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The number of bits /// See [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetMaxcode(int bitCount) - { - return (1 << bitCount) - 1; - } + private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1; /// /// Add a character to the end of the current packet, and if it is 254 characters, @@ -240,25 +244,16 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Reset the code table. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ResetCodeTable() - { - this.hashTable.GetSpan().Fill(-1); - } + private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1); /// /// Compress the packets to the stream. /// - /// The span of indexed pixels. + /// The 2D buffer of indexed pixels. /// The initial bits. /// The stream to write to. - private void Compress(ReadOnlySpan indexedPixels, int initialBits, Stream stream) + private void Compress(Buffer2D indexedPixels, int initialBits, Stream stream) { - int fcode; - int c; - int ent; - int hsizeReg; - int hshift; - // Set up the globals: globalInitialBits - initial number of bits this.globalInitialBits = initialBits; @@ -266,92 +261,82 @@ namespace SixLabors.ImageSharp.Formats.Gif this.clearFlag = false; this.bitCount = this.globalInitialBits; this.maxCode = GetMaxcode(this.bitCount); - this.clearCode = 1 << (initialBits - 1); this.eofCode = this.clearCode + 1; this.freeEntry = this.clearCode + 2; + this.accumulatorCount = 0; // Clear packet - this.accumulatorCount = 0; // clear packet - - ent = this.NextPixel(indexedPixels); - - // TODO: PERF: It looks likt hshift could be calculated once statically. - hshift = 0; - for (fcode = this.hsize; fcode < 65536; fcode *= 2) - { - ++hshift; - } - - hshift = 8 - hshift; // set hash code range bound - - hsizeReg = this.hsize; - - this.ResetCodeTable(); // clear hash table - + this.ResetCodeTable(); // Clear hash table this.Output(this.clearCode, stream); ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); - while (this.position < indexedPixels.Length) - { - c = this.NextPixel(indexedPixels); + int entry = indexedPixels[0, 0]; - fcode = (c << MaxBits) + ent; - int i = (c << hshift) ^ ent /* = 0 */; + for (int y = 0; y < indexedPixels.Height; y++) + { + ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y)); + int offsetX = y == 0 ? 1 : 0; - if (Unsafe.Add(ref hashTableRef, i) == fcode) + for (int x = offsetX; x < indexedPixels.Width; x++) { - ent = Unsafe.Add(ref codeTableRef, i); - continue; - } + int code = Unsafe.Add(ref rowSpanRef, x); + int freeCode = (code << MaxBits) + entry; + int hashIndex = (code << HashShift) ^ entry; - // Non-empty slot - if (Unsafe.Add(ref hashTableRef, i) >= 0) - { - int disp = 1; - if (i != 0) + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - disp = hsizeReg - i; + entry = Unsafe.Add(ref codeTableRef, hashIndex); + continue; } - do + // Non-empty slot + if (Unsafe.Add(ref hashTableRef, hashIndex) >= 0) { - if ((i -= disp) < 0) + int disp = 1; + if (hashIndex != 0) + { + disp = HashSize - hashIndex; + } + + do { - i += hsizeReg; + if ((hashIndex -= disp) < 0) + { + hashIndex += HashSize; + } + + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) + { + entry = Unsafe.Add(ref codeTableRef, hashIndex); + break; + } } + while (Unsafe.Add(ref hashTableRef, hashIndex) >= 0); - if (Unsafe.Add(ref hashTableRef, i) == fcode) + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - ent = Unsafe.Add(ref codeTableRef, i); - break; + continue; } } - while (Unsafe.Add(ref hashTableRef, i) >= 0); - if (Unsafe.Add(ref hashTableRef, i) == fcode) + this.Output(entry, stream); + entry = code; + if (this.freeEntry < MaxMaxCode) { - continue; + Unsafe.Add(ref codeTableRef, hashIndex) = this.freeEntry++; // code -> hashtable + Unsafe.Add(ref hashTableRef, hashIndex) = freeCode; + } + else + { + this.ClearBlock(stream); } - } - - this.Output(ent, stream); - ent = c; - if (this.freeEntry < MaxMaxCode) - { - Unsafe.Add(ref codeTableRef, i) = this.freeEntry++; // code -> hashtable - Unsafe.Add(ref hashTableRef, i) = fcode; - } - else - { - this.ClearBlock(stream); } } - // Put out the final code. - this.Output(ent, stream); - + // Output the final code. + this.Output(entry, stream); this.Output(this.eofCode, stream); } @@ -367,19 +352,6 @@ namespace SixLabors.ImageSharp.Formats.Gif this.accumulatorCount = 0; } - /// - /// Reads the next pixel from the image. - /// - /// The span of indexed pixels. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int NextPixel(ReadOnlySpan indexedPixels) - { - return indexedPixels[this.position++] & 0xFF; - } - /// /// Output the current code to the stream. /// diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs new file mode 100644 index 0000000000..7c432d26fe --- /dev/null +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the gif format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static GifMetadata GetGifMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); + + /// + /// Gets the gif format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Gif/README.md b/src/ImageSharp/Formats/Gif/README.md index d47a4c6836..eeda20c06a 100644 --- a/src/ImageSharp/Formats/Gif/README.md +++ b/src/ImageSharp/Formats/Gif/README.md @@ -1,4 +1,6 @@ -Encoder/Decoder adapted and extended from: +Encoder/Decoder adapted and extended from: -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ +- [Nine.Imaging](https://github.com/yufeih/Nine.Imaging/) +- [imagetools.codeplex](https://imagetools.codeplex.com/) + +A useful set of gif test images can be found at [pygif](https://github.com/robert-ancell/pygif/tree/master/test-suite) \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index 8af5b81c20..5e26370bae 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Gif buffer[0] = GifConstants.ApplicationBlockSize; // Write NETSCAPE2.0 - GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11)); + GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); // Application Data ---- buffer[12] = 3; // Application block length (always 3) diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 8dafdac795..7a7fc4b263 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -17,17 +17,18 @@ namespace SixLabors.ImageSharp.Formats /// The pixel format. /// The configuration for the image. /// The containing image data. - /// The decoded image of a given pixel type. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; /// /// Decodes the image from the specified stream to an . - /// The decoder is free to choose the pixel type. /// /// The configuration for the image. /// The containing image data. - /// The decoded image of a pixel type chosen by the decoder. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream); } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 76d831d5aa..d5ff4b93c4 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -18,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats /// The to encode from. /// The to encode the image data to. void Encode(Image image, Stream stream) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 0b69e3f8ba..acde84c912 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public Block8x8(Span coefficients) { ref byte selfRef = ref Unsafe.As(ref this); - ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(coefficients)); + ref byte sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(coefficients)); Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs deleted file mode 100644 index 6bf9c8483a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -// ReSharper disable UseObjectOrCollectionInitializer -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - internal partial struct Block8x8F - { - /// - /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void CopyTo(in BufferArea area, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - this.Copy1x1Scale(area); - return; - } - - if (horizontalScale == 2 && verticalScale == 2) - { - this.Copy2x2Scale(area); - return; - } - - // TODO: Optimize: implement all cases with scale-specific, loopless code! - this.CopyArbitraryScale(area, horizontalScale, verticalScale); - } - - public void Copy1x1Scale(in BufferArea destination) - { - ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref destination.GetReferenceToOrigin()); - int destStride = destination.Stride * sizeof(float); - - CopyRowImpl(ref selfBase, ref destBase, destStride, 0); - CopyRowImpl(ref selfBase, ref destBase, destStride, 1); - CopyRowImpl(ref selfBase, ref destBase, destStride, 2); - CopyRowImpl(ref selfBase, ref destBase, destStride, 3); - CopyRowImpl(ref selfBase, ref destBase, destStride, 4); - CopyRowImpl(ref selfBase, ref destBase, destStride, 5); - CopyRowImpl(ref selfBase, ref destBase, destStride, 6); - CopyRowImpl(ref selfBase, ref destBase, destStride, 7); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) - { - ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); - ref byte d = ref Unsafe.Add(ref destBase, row * destStride); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } - - private void Copy2x2Scale(in BufferArea area) - { - ref Vector2 destBase = ref Unsafe.As(ref area.GetReferenceToOrigin()); - int destStride = area.Stride / 2; - - this.WidenCopyRowImpl2x2(ref destBase, 0, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 1, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 2, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 3, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 4, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 5, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 6, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - int offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); - - var xyLeft = new Vector4(sLeft.X); - xyLeft.Z = sLeft.Y; - xyLeft.W = sLeft.Y; - - var zwLeft = new Vector4(sLeft.Z); - zwLeft.Z = sLeft.W; - zwLeft.W = sLeft.W; - - var xyRight = new Vector4(sRight.X); - xyRight.Z = sRight.Y; - xyRight.W = sRight.Y; - - var zwRight = new Vector4(sRight.Z); - zwRight.Z = sRight.W; - zwRight.W = sRight.W; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; - } - - [MethodImpl(InliningOptions.ColdPath)] - private void CopyArbitraryScale(BufferArea area, int horizontalScale, int verticalScale) - { - ref float destBase = ref area.GetReferenceToOrigin(); - - for (int y = 0; y < 8; y++) - { - int yy = y * verticalScale; - int y8 = y * 8; - - for (int x = 0; x < 8; x++) - { - int xx = x * horizontalScale; - - float value = this[y8 + x]; - - for (int i = 0; i < verticalScale; i++) - { - int baseIdx = ((yy + i) * area.Stride) + xx; - - for (int j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref destBase, baseIdx + j) = value; - } - } - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 23b51f0926..8e14ed2c36 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -99,29 +99,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components var CMax4 = new Vector4(maximum); var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); - this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); - this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); - this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4); - this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4); - this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4); - this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4); - this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4); - this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4); - this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4); - this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4); - this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4); - this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4); - this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4); - this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4); - this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4); - this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4); + this.V0L = Vector4Utilities.FastClamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Vector4Utilities.FastClamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Vector4Utilities.FastClamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Vector4Utilities.FastClamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Vector4Utilities.FastClamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Vector4Utilities.FastClamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Vector4Utilities.FastClamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Vector4Utilities.FastClamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Vector4Utilities.FastClamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Vector4Utilities.FastClamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Vector4Utilities.FastClamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Vector4Utilities.FastClamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Vector4Utilities.FastClamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Vector4Utilities.FastClamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Vector4Utilities.FastClamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Vector4Utilities.FastClamp(this.V7R + COff4, CMin4, CMax4); } /// /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + public void NormalizeColorsAndRoundInplaceVector8(float maximum) { var off = new Vector(MathF.Ceiling(maximum / 2)); var max = new Vector(maximum); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index 176591972a..a1a6b01726 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components for (int j = 0; j < 2; j++) { char side = j == 0 ? 'L' : 'R'; - Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); + Write($"this.V{i}{side} = Vector4Utilities.FastClamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); } } PopIndent(); @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + public void NormalizeColorsAndRoundInplaceVector8(float maximum) { var off = new Vector(MathF.Ceiling(maximum / 2)); var max = new Vector(maximum); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs new file mode 100644 index 0000000000..064ca75539 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal partial struct Block8x8F + { + /// + /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(in BufferArea area, int horizontalScale, int verticalScale) + { + ref float areaOrigin = ref area.GetReferenceToOrigin(); + this.ScaledCopyTo(ref areaOrigin, area.Stride, horizontalScale, verticalScale); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + this.Copy1x1Scale(ref areaOrigin, areaStride); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + this.Copy2x2Scale(ref areaOrigin, areaStride); + return; + } + + // TODO: Optimize: implement all cases with scale-specific, loopless code! + this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); + } + + public void Copy1x1Scale(ref float areaOrigin, int areaStride) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref selfBase, ref destBase, destStride, 0); + CopyRowImpl(ref selfBase, ref destBase, destStride, 1); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + + private void Copy2x2Scale(ref float areaOrigin, int areaStride) + { + ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride / 2; + + this.WidenCopyRowImpl2x2(ref destBase, 0, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 1, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 2, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 3, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 4, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 5, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 6, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + int offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X); + xyLeft.Z = sLeft.Y; + xyLeft.W = sLeft.Y; + + var zwLeft = new Vector4(sLeft.Z); + zwLeft.Z = sLeft.W; + zwLeft.W = sLeft.W; + + var xyRight = new Vector4(sRight.X); + xyRight.Z = sRight.Y; + xyRight.W = sRight.Y; + + var zwRight = new Vector4(sRight.Z); + zwRight.Z = sRight.W; + zwRight.W = sRight.W; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; + } + + [MethodImpl(InliningOptions.ColdPath)] + private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * areaStride) + xx; + + for (int j = 0; j < horizontalScale; j++) + { + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; + } + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index f11b0f8fa7..70a34ddcfa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public void CopyTo(Span dest) + public void ScaledCopyTo(Span dest) { ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); ref byte s = ref Unsafe.As(ref this); @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Pointer to block /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) { float* fPtr = (float*)blockPtr; for (int i = 0; i < Size; i++) @@ -231,9 +231,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// The block pointer. /// The destination. [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) { - blockPtr->CopyTo(dest); + blockPtr->ScaledCopyTo(dest); } /// @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void CopyTo(float[] dest) + public unsafe void ScaledCopyTo(float[] dest) { fixed (void* ptr = &this.V0L) { @@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Copy raw 32bit floating point data to dest /// /// Destination - public unsafe void CopyTo(Span dest) + public unsafe void ScaledCopyTo(Span dest) { fixed (Vector4* ptr = &this.V0L) { @@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public float[] ToArray() { var result = new float[Size]; - this.CopyTo(result); + this.ScaledCopyTo(result); return result; } @@ -471,9 +471,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// public void NormalizeColorsAndRoundInplace(float maximum) { - if (SimdUtils.IsAvx2CompatibleArchitecture) + if (SimdUtils.HasVector8) { - this.NormalizeColorsAndRoundInplaceAvx2(maximum); + this.NormalizeColorsAndRoundInplaceVector8(maximum); } else { @@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public void LoadFrom(ref Block8x8 source) { #if SUPPORTS_EXTENDED_INTRINSICS - if (SimdUtils.IsAvx2CompatibleArchitecture) + if (SimdUtils.HasVector8) { this.LoadFromInt16ExtendedAvx2(ref source); return; @@ -513,7 +513,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public void LoadFromInt16ExtendedAvx2(ref Block8x8 source) { DebugGuard.IsTrue( - SimdUtils.IsAvx2CompatibleArchitecture, + SimdUtils.HasVector8, "LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!"); ref Vector sRef = ref Unsafe.As>(ref source); @@ -589,7 +589,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { // sign(dividend) = max(min(dividend, 1), -1) - var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One); + var sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One); // AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend) return (dividend / divisor) + (sign * Offset); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs index 1706b4c1bc..002f79f84c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -90,21 +90,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters if (Vector.Count == 4) { // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) - r.RoundAndDownscalePreAvx2(maxValue); - g.RoundAndDownscalePreAvx2(maxValue); - b.RoundAndDownscalePreAvx2(maxValue); + r.RoundAndDownscalePreVector8(maxValue); + g.RoundAndDownscalePreVector8(maxValue); + b.RoundAndDownscalePreVector8(maxValue); } - else if (SimdUtils.IsAvx2CompatibleArchitecture) + else if (SimdUtils.HasVector8) { - r.RoundAndDownscaleAvx2(maxValue); - g.RoundAndDownscaleAvx2(maxValue); - b.RoundAndDownscaleAvx2(maxValue); + r.RoundAndDownscaleVector8(maxValue); + g.RoundAndDownscaleVector8(maxValue); + b.RoundAndDownscaleVector8(maxValue); } else { // TODO: Run fallback scalar code here // However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007 - throw new NotImplementedException("Your CPU architecture is too modern!"); + JpegThrowHelper.ThrowNotImplementedException("Your CPU architecture is too modern!"); } // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: @@ -114,4 +114,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs index 093ea2f9a2..8c1b427ee5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -13,14 +13,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal sealed class FromYCbCrSimdAvx2 : JpegColorConverter + internal sealed class FromYCbCrSimdVector8 : JpegColorConverter { - public FromYCbCrSimdAvx2(int precision) + public FromYCbCrSimdVector8(int precision) : base(JpegColorSpace.YCbCr, precision) { } - public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture; + public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.HasVector8; public override void ConvertToRgba(in ComponentValues values, Span result) { @@ -107,4 +107,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 61e3598696..7ada1b9da4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision) + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision) { JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace && c.Precision == precision); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// Returns the for the YCbCr colorspace that matches the current CPU architecture. /// private static JpegColorConverter GetYCbCrConverter(int precision) => - FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision); + FromYCbCrSimdVector8.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdVector8(precision) : new FromYCbCrSimd(precision); /// /// A stack-only struct to reference the input buffers using -s. @@ -232,4 +232,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs index 13c89c82cf..34fe1aecbd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder [MethodImpl(InliningOptions.ShortMethod)] public void CheckBits() { - if (this.remainingBits < 16) + if (this.remainingBits < JpegConstants.Huffman.MinBits) { this.FillBuffer(); } @@ -85,8 +85,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { // Attempt to load at least the minimum number of required bits into the buffer. // We fail to do so only if we hit a marker or reach the end of the input stream. - this.remainingBits += 48; - this.data = (this.data << 48) | this.GetBytes(); + this.remainingBits += JpegConstants.Huffman.FetchBits; + this.data = (this.data << JpegConstants.Huffman.FetchBits) | this.GetBytes(); } [MethodImpl(InliningOptions.ShortMethod)] @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private ulong GetBytes() { ulong temp = 0; - for (int i = 0; i < 6; i++) + for (int i = 0; i < JpegConstants.Huffman.FetchLoop; i++) { int b = this.ReadStream(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 4685ba2895..6025930169 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -82,12 +82,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Figure C.1: make table of Huffman code length for each symbol int p = 0; - for (int l = 1; l <= 16; l++) + for (int j = 1; j <= 16; j++) { - int i = this.Sizes[l]; + int i = this.Sizes[j]; while (i-- != 0) { - huffSize[p++] = (char)l; + huffSize[p++] = (char)j; } } @@ -111,20 +111,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Figure F.15: generate decoding tables for bit-sequential decoding p = 0; - for (int l = 1; l <= 16; l++) + for (int j = 1; j <= 16; j++) { - if (this.Sizes[l] != 0) + if (this.Sizes[j] != 0) { - int offset = p - (int)huffCode[p]; - this.ValOffset[l] = offset; - p += this.Sizes[l]; - this.MaxCode[l] = huffCode[p - 1]; // Maximum code of length l - this.MaxCode[l] <<= 64 - l; // Left justify - this.MaxCode[l] |= (1ul << (64 - l)) - 1; + this.ValOffset[j] = p - (int)huffCode[p]; + p += this.Sizes[j]; + this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l + this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify + this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; } else { - this.MaxCode[l] = 0; + this.MaxCode[j] = 0; } } @@ -142,11 +141,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder p = 0; for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) { + int jShift = JpegConstants.Huffman.LookupBits - length; for (int i = 1; i <= this.Sizes[length]; i++, p++) { // length = current code's length, p = its index in huffCode[] & Values[]. // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << (JpegConstants.Huffman.LookupBits - length)); + int lookBits = (int)(huffCode[p] << jShift); for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { this.LookaheadSize[lookBits] = (byte)length; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs index 2492a985a8..169b02e9fb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index 2f393fadae..8075fd4bab 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -3,8 +3,6 @@ using System; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index 7497c8a409..44878bd6c7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (xDensity <= 0) { - JpegThrowHelper.ThrowImageFormatException($"X-Density {xDensity} must be greater than 0."); + JpegThrowHelper.ThrowInvalidImageContentException($"X-Density {xDensity} must be greater than 0."); } if (yDensity <= 0) { - JpegThrowHelper.ThrowImageFormatException($"Y-Density {yDensity} must be greater than 0."); + JpegThrowHelper.ThrowInvalidImageContentException($"Y-Density {yDensity} must be greater than 0."); } this.MajorVersion = majorVersion; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index c5efb812e0..db4b6a532b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -69,11 +68,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . /// /// The source block. - /// The destination buffer area. + /// Reference to the origin of the destination pixel area. + /// The width of the destination pixel buffer. /// The maximum value derived from the bitdepth. public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, - in BufferArea destArea, + ref float destAreaOrigin, + int destAreaStride, float maximumValue) { ref Block8x8F b = ref this.SourceBlock; @@ -89,7 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // To be "more accurate", we need to emulate this by rounding! this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue); - this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); + this.WorkspaceBlock1.ScaledCopyTo( + ref destAreaOrigin, + destAreaStride, + this.subSamplingDivisors.Width, + this.subSamplingDivisors.Height); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 5353303947..622c34e9bb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -1,11 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -22,11 +20,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.Frame = frame; this.Id = id; - // Valid sampling factors are 1..2 - if (horizontalFactor == 0 - || verticalFactor == 0 - || horizontalFactor > 2 - || verticalFactor > 2) + // Validate sampling factors. + if (horizontalFactor == 0 || verticalFactor == 0) { JpegThrowHelper.ThrowBadSampling(); } @@ -138,4 +133,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.SpectralBlocks = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index e1a9380a03..d9fd9ac8b5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -6,8 +6,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -33,12 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.Component = component; this.ImagePostProcessor = imagePostProcessor; - this.ColorBuffer = memoryAllocator.Allocate2D( + this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( imagePostProcessor.PostProcessorBufferSize.Width, - imagePostProcessor.PostProcessorBufferSize.Height); + imagePostProcessor.PostProcessorBufferSize.Height, + this.blockAreaSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; - this.blockAreaSize = this.Component.SubSamplingDivisors * 8; } /// @@ -80,6 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; + int destAreaStride = this.ColorBuffer.Width; + for (int y = 0; y < this.BlockRowsPerStep; y++) { int yBlock = this.currentComponentRowInBlocks + y; @@ -91,26 +92,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; + Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); - ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow); + // see: https://github.com/SixLabors/ImageSharp/issues/824 + int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width); - for (int xBlock = 0; xBlock < this.SizeInBlocks.Width; xBlock++) + for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) { - ref Block8x8 block = ref Unsafe.Add(ref blockRowBase, xBlock); + ref Block8x8 block = ref blockRow[xBlock]; int xBuffer = xBlock * this.blockAreaSize.Width; + ref float destAreaOrigin = ref colorBufferRow[xBuffer]; - BufferArea destArea = this.ColorBuffer.GetArea( - xBuffer, - yBuffer, - this.blockAreaSize.Width, - this.blockAreaSize.Height); - - blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue); + blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); } } this.currentComponentRowInBlocks += this.BlockRowsPerStep; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index f3f2952b1d..5352a0bff7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -7,8 +7,6 @@ using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder @@ -114,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image public void PostProcess(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.PixelRowCounter = 0; @@ -135,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image. public void DoPostProcessorStep(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) { @@ -153,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image private void ConvertColorsInto(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index 54633a5d72..325d7780ae 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using System.Text; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -12,24 +11,62 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal static class ProfileResolver { /// - /// Describes the JFIF specific markers. + /// Gets the JFIF specific markers. /// - public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + public static ReadOnlySpan JFifMarker => new[] + { + (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' + }; + + /// + /// Gets the ICC specific markers. + /// + public static ReadOnlySpan IccMarker => new[] + { + (byte)'I', (byte)'C', (byte)'C', (byte)'_', + (byte)'P', (byte)'R', (byte)'O', (byte)'F', + (byte)'I', (byte)'L', (byte)'E', (byte)'\0' + }; + + /// + /// Gets the adobe photoshop APP13 marker which can contain IPTC meta data. + /// + public static ReadOnlySpan AdobePhotoshopApp13Marker => new[] + { + (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', (byte)'\0' + }; + + /// + /// Gets the 8BIM marker, which signals the start of a adobe specific image resource block. + /// + public static ReadOnlySpan AdobeImageResourceBlockMarker => new[] + { + (byte)'8', (byte)'B', (byte)'I', (byte)'M' + }; /// - /// Describes the ICC specific markers. + /// Gets a IPTC Image resource ID. /// - public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + public static ReadOnlySpan AdobeIptcMarker => new[] + { + (byte)4, (byte)4 + }; /// - /// Describes the EXIF specific markers. + /// Gets the EXIF specific markers. /// - public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + public static ReadOnlySpan ExifMarker => new[] + { + (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' + }; /// - /// Describes Adobe specific markers . + /// Gets the Adobe specific markers . /// - public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); + public static ReadOnlySpan AdobeMarker => new[] + { + (byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e' + }; /// /// Returns a value indicating whether the passed bytes are a match to the profile identifier. @@ -43,4 +80,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder && bytesToCheck.Slice(0, profileIdentifier.Length).SequenceEqual(profileIdentifier); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 883a085b5a..ba604e8910 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The pixel type to work on internal ref struct YCbCrForwardConverter - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// The Y component @@ -55,12 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) /// - public void Convert(ImageFrame frame, int x, int y) + public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows) { - this.pixelBlock.LoadAndStretchEdges(frame, x, y); + this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows); Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); - PixelOperations.Instance.ToRgb24(frame.Configuration, this.pixelBlock.AsSpanUnsafe(), rgbSpan); + PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); ref float yBlockStart = ref Unsafe.As(ref this.Y); ref float cbBlockStart = ref Unsafe.As(ref this.Cb); @@ -81,4 +81,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs index 3d1e22a99d..534c66b99d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -54,24 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY) - where TPixel : struct, IPixel - { - if (source.PixelBuffer is Buffer2D buffer) - { - this.LoadAndStretchEdges(buffer, sourceX, sourceY); - } - else - { - throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); - } - } - /// /// Load a 8x8 region of an image into the block. /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image. /// - public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY) + public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows) { int width = Math.Min(8, source.Width - sourceX); int height = Math.Min(8, source.Height - sourceY); @@ -85,15 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components int remainderXCount = 8 - width; ref byte blockStart = ref Unsafe.As, byte>(ref this); - ref byte imageStart = ref Unsafe.As( - ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX)); - int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); - int imageRowSizeInBytes = source.Width * Unsafe.SizeOf(); for (int y = 0; y < height; y++) { - ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); + Span row = currentRows[y]; + + ref byte s = ref Unsafe.As(ref row[sourceX]); ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); Unsafe.CopyBlock(ref d, ref s, byteWidth); @@ -127,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs new file mode 100644 index 0000000000..8c3daa4d50 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + /// + /// Cache 8 pixel rows on the stack, which may originate from different buffers of a . + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly ref struct RowOctet + where T : struct + { + private readonly Span row0; + private readonly Span row1; + private readonly Span row2; + private readonly Span row3; + private readonly Span row4; + private readonly Span row5; + private readonly Span row6; + private readonly Span row7; + + public RowOctet(Buffer2D buffer, int startY) + { + int y = startY; + int height = buffer.Height; + this.row0 = y < height ? buffer.GetRowSpan(y++) : default; + this.row1 = y < height ? buffer.GetRowSpan(y++) : default; + this.row2 = y < height ? buffer.GetRowSpan(y++) : default; + this.row3 = y < height ? buffer.GetRowSpan(y++) : default; + this.row4 = y < height ? buffer.GetRowSpan(y++) : default; + this.row5 = y < height ? buffer.GetRowSpan(y++) : default; + this.row6 = y < height ? buffer.GetRowSpan(y++) : default; + this.row7 = y < height ? buffer.GetRowSpan(y) : default; + } + + public Span this[int y] + { + [MethodImpl(InliningOptions.ShortMethod)] + get + { + // No unsafe tricks, since Span can't be used as a generic argument + return y switch + { + 0 => this.row0, + 1 => this.row1, + 2 => this.row2, + 3 => this.row3, + 4 => this.row4, + 5 => this.row5, + 6 => this.row6, + 7 => this.row7, + _ => ThrowIndexOutOfRangeException() + }; + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static Span ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs index 48ad188561..94771aa64c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs @@ -4,8 +4,6 @@ using System; using System.Numerics; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index 059e2052b3..669abad28c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -34,12 +34,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public fixed byte Data[Size]; /// - /// Unzig maps from the zigzag ordering to the natural ordering. For example, - /// unzig[3] is the column and row of the fourth element in zigzag order. The - /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// Gets the unzigs map, which maps from the zigzag ordering to the natural ordering. + /// For example, unzig[3] is the column and row of the fourth element in zigzag order. + /// The value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). /// - private static readonly byte[] Unzig = - new byte[Size] + private static ReadOnlySpan Unzig => new byte[] { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, @@ -75,8 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public static ZigZag CreateUnzigTable() { ZigZag result = default; - byte* unzigPtr = result.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, Size); + ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig); + ref byte destinationRef = ref Unsafe.AsRef(result.Data); + + Unzig.CopyTo(new Span(result.Data, Size)); + return result; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index a39480e126..9f50e2cab1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; namespace SixLabors.ImageSharp.Formats.Jpeg { @@ -249,6 +250,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public const int RegisterSize = 64; + /// + /// The number of bits to fetch when filling the buffer. + /// + public const int FetchBits = 48; + + /// + /// The number of times to read the input stream when filling the buffer. + /// + public const int FetchLoop = FetchBits / 8; + + /// + /// The minimum number of bits allowed before by the before fetching. + /// + public const int MinBits = RegisterSize - FetchBits; + /// /// If the next Huffman code is no more than this number of bits, we can obtain its length /// and the corresponding symbol directly from this tables. @@ -266,4 +282,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public const int LookupSize = 1 << LookupBits; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index a1bf048521..e60901d913 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -18,16 +19,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - using (var decoder = new JpegDecoderCore(configuration, this)) + using var decoder = new JpegDecoderCore(configuration, this); + try { return decoder.Decode(stream); } + catch (InvalidMemoryOperationException ex) + { + (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); + + JpegThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); + + // Not reachable, as the previous statement will throw a exception. + return null; + } } + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + /// public IImageInfo Identify(Configuration configuration, Stream stream) { @@ -38,8 +53,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return decoder.Identify(stream); } } - - /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index c15fe5e274..654610659d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -14,10 +14,8 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg { @@ -49,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private readonly byte[] markerBuffer = new byte[2]; /// - /// The DC Huffman tables + /// The DC Huffman tables. /// private HuffmanTable[] dcHuffmanTables; @@ -59,37 +57,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private HuffmanTable[] acHuffmanTables; /// - /// The reset interval determined by RST markers + /// The reset interval determined by RST markers. /// private ushort resetInterval; /// - /// Whether the image has an EXIF marker + /// Whether the image has an EXIF marker. /// private bool isExif; /// - /// Contains exif data + /// Contains exif data. /// private byte[] exifData; /// - /// Whether the image has an ICC marker + /// Whether the image has an ICC marker. /// private bool isIcc; /// - /// Contains ICC data + /// Contains ICC data. /// private byte[] iccData; /// - /// Contains information about the JFIF marker + /// Whether the image has a IPTC data. + /// + private bool isIptc; + + /// + /// Contains IPTC data. + /// + private byte[] iptcData; + + /// + /// Contains information about the JFIF marker. /// private JFifMarker jFif; /// - /// Contains information about the Adobe marker + /// Contains information about the Adobe marker. /// private AdobeMarker adobe; @@ -211,11 +219,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The stream, where the image should be. /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.ParseStream(stream); this.InitExifProfile(); this.InitIccProfile(); + this.InitIptcProfile(); this.InitDerivedMetadataProperties(); return this.PostProcessIntoImage(); } @@ -229,6 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.ParseStream(stream, true); this.InitExifProfile(); this.InitIccProfile(); + this.InitIptcProfile(); this.InitDerivedMetadataProperties(); return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); @@ -249,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { - JpegThrowHelper.ThrowImageFormatException("Missing SOI marker."); + JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } this.InputStream.Read(this.markerBuffer, 0, 2); @@ -347,10 +357,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: - case JpegConstants.Markers.APP13: this.InputStream.Skip(remaining); break; + case JpegConstants.Markers.APP13: + this.ProcessApp13Marker(remaining); + break; + case JpegConstants.Markers.APP14: this.ProcessApp14Marker(remaining); break; @@ -410,7 +423,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg : JpegColorSpace.Cmyk; } - JpegThrowHelper.ThrowImageFormatException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); return default; } @@ -440,6 +453,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + /// + /// Initializes the IPTC profile. + /// + private void InitIptcProfile() + { + if (this.isIptc) + { + var profile = new IptcProfile(this.iptcData); + this.Metadata.IptcProfile = profile; + } + } + /// /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// @@ -465,24 +490,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } - private double GetExifResolutionValue(ExifTag tag) + private double GetExifResolutionValue(ExifTag tag) { - if (!this.Metadata.ExifProfile.TryGetValue(tag, out ExifValue exifValue)) - { - return 0; - } + IExifValue resolution = this.Metadata.ExifProfile.GetValue(tag); - switch (exifValue.DataType) - { - case ExifDataType.Rational: - return ((Rational)exifValue.Value).ToDouble(); - case ExifDataType.Long: - return (uint)exifValue.Value; - case ExifDataType.DoubleFloat: - return (double)exifValue.Value; - default: - return 0; - } + return resolution is null ? 0 : resolution.Value.ToDouble(); } /// @@ -598,6 +610,95 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + /// + /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. + /// The content of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks. + /// + /// The remaining bytes in the segment block. + private void ProcessApp13Marker(int remaining) + { + if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata) + { + this.InputStream.Skip(remaining); + return; + } + + this.InputStream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length); + remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) + { + var resourceBlockData = new byte[remaining]; + this.InputStream.Read(resourceBlockData, 0, remaining); + Span blockDataSpan = resourceBlockData.AsSpan(); + + while (blockDataSpan.Length > 12) + { + if (!ProfileResolver.IsProfile(blockDataSpan.Slice(0, 4), ProfileResolver.AdobeImageResourceBlockMarker)) + { + return; + } + + blockDataSpan = blockDataSpan.Slice(4); + Span imageResourceBlockId = blockDataSpan.Slice(0, 2); + if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) + { + var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int dataStartIdx = 2 + resourceBlockNameLength + 4; + if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) + { + this.isIptc = true; + this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); + break; + } + } + else + { + var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int dataStartIdx = 2 + resourceBlockNameLength + 4; + if (blockDataSpan.Length < dataStartIdx + resourceDataSize) + { + // Not enough data or the resource data size is wrong. + break; + } + + blockDataSpan = blockDataSpan.Slice(dataStartIdx + resourceDataSize); + } + } + } + } + + /// + /// Reads the adobe image resource block name: a Pascal string (padded to make size even). + /// + /// The span holding the block resource data. + /// The length of the name. + [MethodImpl(InliningOptions.ShortMethod)] + private static int ReadImageResourceNameLength(Span blockDataSpan) + { + byte nameLength = blockDataSpan[2]; + var nameDataSize = nameLength == 0 ? 2 : nameLength; + if (nameDataSize % 2 != 0) + { + nameDataSize++; + } + + return nameDataSize; + } + + /// + /// Reads the length of a adobe image resource data block. + /// + /// The span holding the block resource data. + /// The length of the block name. + /// The block length. + [MethodImpl(InliningOptions.ShortMethod)] + private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength) + { + return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4)); + } + /// /// Processes the application header containing the Adobe identifier /// which stores image encoding information for DCT filters. @@ -649,48 +750,51 @@ namespace SixLabors.ImageSharp.Formats.Jpeg switch (quantizationTableSpec >> 4) { case 0: + { + // 8 bit values + if (remaining < 64) { - // 8 bit values - if (remaining < 64) - { - done = true; - break; - } + done = true; + break; + } - this.InputStream.Read(this.temp, 0, 64); - remaining -= 64; + this.InputStream.Read(this.temp, 0, 64); + remaining -= 64; - ref Block8x8F table = ref this.QuantizationTables[tableIndex]; - for (int j = 0; j < 64; j++) - { - table[j] = this.temp[j]; - } + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; + for (int j = 0; j < 64; j++) + { + table[j] = this.temp[j]; } + } - break; + break; case 1: + { + // 16 bit values + if (remaining < 128) { - // 16 bit values - if (remaining < 128) - { - done = true; - break; - } + done = true; + break; + } - this.InputStream.Read(this.temp, 0, 128); - remaining -= 128; + this.InputStream.Read(this.temp, 0, 128); + remaining -= 128; - ref Block8x8F table = ref this.QuantizationTables[tableIndex]; - for (int j = 0; j < 64; j++) - { - table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; - } + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; + for (int j = 0; j < 64; j++) + { + table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; } + } + + break; - break; default: + { JpegThrowHelper.ThrowBadQuantizationTable(); break; + } } if (done) @@ -717,7 +821,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { if (this.Frame != null) { - JpegThrowHelper.ThrowImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); + JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); } // Read initial marker definitions. @@ -727,7 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // We only support 8-bit and 12-bit precision. if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) { - JpegThrowHelper.ThrowImageFormatException("Only 8-Bit and 12-Bit precision supported."); + JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); } this.Precision = this.temp[0]; @@ -774,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg for (int i = 0; i < this.ComponentCount; i++) { byte hv = this.temp[index + 1]; - int h = hv >> 4; + int h = (hv >> 4) & 15; int v = hv & 15; if (maxH < h) @@ -824,13 +928,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Types 0..1 DC..AC if (tableType > 1) { - JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table type."); + JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table type."); } // Max tables of each type if (tableIndex > 3) { - JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table index."); + JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index."); } this.InputStream.Read(huffmanData.Array, 0, 16); @@ -849,7 +953,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (codeLengthSum > 256 || codeLengthSum > length) { - JpegThrowHelper.ThrowImageFormatException("Huffman table has excessive length."); + JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); } using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) @@ -891,7 +995,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { if (this.Frame is null) { - JpegThrowHelper.ThrowImageFormatException("No readable SOFn (Start Of Frame) marker found."); + JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); } int selectorsCount = this.InputStream.ReadByte(); @@ -912,7 +1016,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (componentIndex < 0) { - JpegThrowHelper.ThrowImageFormatException($"Unknown component selector {componentIndex}."); + JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}."); } ref JpegComponent component = ref this.Frame.Components[componentIndex]; @@ -971,7 +1075,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The . private Image PostProcessIntoImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.ImageWidth == 0 || this.ImageHeight == 0) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index d649d30418..1c4035a981 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new JpegEncoderCore(this); encoder.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 0b9eeb609b..eed95c6b07 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -1,17 +1,20 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers.Binary; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -191,7 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The image to write from. /// The stream to write to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -206,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ImageMetadata metadata = image.Metadata; // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. - int qlty = (this.quality ?? metadata.GetFormatMetadata(JpegFormat.Instance).Quality).Clamp(1, 100); + int qlty = (this.quality ?? metadata.GetJpegMetadata().Quality).Clamp(1, 100); this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); // Convert from a quality rating to a scaling factor. @@ -230,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the Start Of Image marker. this.WriteApplicationHeader(metadata); - // Write Exif and ICC profiles + // Write Exif, ICC and IPTC profiles this.WriteProfiles(metadata); // Write the quantization tables. @@ -393,7 +396,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode444(Image pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // (Partially done with YCbCrForwardConverter) @@ -409,12 +412,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; var pixelConverter = YCbCrForwardConverter.Create(); + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 8) { + var currentRows = new RowOctet(pixelBuffer, y); + for (int x = 0; x < pixels.Width; x += 8) { - pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + pixelConverter.Convert(frame, x, y, currentRows); prevDCY = this.WriteBlock( QuantIndex.Luminance, @@ -642,12 +649,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Writes the EXIF profile. /// /// The exif profile. - /// - /// Thrown if the EXIF profile size exceeds the limit - /// private void WriteExifProfile(ExifProfile exifProfile) { - if (exifProfile is null) + if (exifProfile is null || exifProfile.Values.Count == 0) { return; } @@ -655,9 +659,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg const int MaxBytesApp1 = 65533; // 64k - 2 padding bytes const int MaxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. - byte[] data = exifProfile?.ToByteArray(); + byte[] data = exifProfile.ToByteArray(); - if (data is null || data.Length == 0) + if (data.Length == 0) { return; } @@ -692,16 +696,68 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + /// + /// Writes the IPTC metadata. + /// + /// The iptc metadata to write. + /// + /// Thrown if the IPTC profile size exceeds the limit of 65533 bytes. + /// + private void WriteIptcProfile(IptcProfile iptcProfile) + { + const int Max = 65533; + if (iptcProfile is null || !iptcProfile.Values.Any()) + { + return; + } + + iptcProfile.UpdateData(); + byte[] data = iptcProfile.Data; + if (data.Length == 0) + { + return; + } + + if (data.Length > Max) + { + throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes"); + } + + var app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length + + ProfileResolver.AdobeImageResourceBlockMarker.Length + + ProfileResolver.AdobeIptcMarker.Length + + 2 + 4 + data.Length; + this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); + this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker); + this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker); + this.outputStream.Write(ProfileResolver.AdobeIptcMarker); + this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) + this.outputStream.WriteByte(0); + BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); + this.outputStream.Write(this.buffer, 0, 4); + this.outputStream.Write(data, 0, data.Length); + } + /// /// Writes the App1 header. /// - /// The length of the data the app1 marker contains + /// The length of the data the app1 marker contains. private void WriteApp1Header(int app1Length) + { + this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1); + } + + /// + /// Writes a AppX header. + /// + /// The length of the data the app marker contains. + /// The app marker to write. + private void WriteAppHeader(int length, byte appMarker) { this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker - this.buffer[2] = (byte)((app1Length >> 8) & 0xFF); - this.buffer[3] = (byte)(app1Length & 0xFF); + this.buffer[1] = appMarker; + this.buffer[2] = (byte)((length >> 8) & 0xFF); + this.buffer[3] = (byte)(length & 0xFF); this.outputStream.Write(this.buffer, 0, 4); } @@ -800,6 +856,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg metadata.SyncProfiles(); this.WriteExifProfile(metadata.ExifProfile); this.WriteIccProfile(metadata.IccProfile); + this.WriteIptcProfile(metadata.IptcProfile); } /// @@ -886,7 +943,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The pixel accessor providing access to the image pixels. private void WriteStartOfScan(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: We should allow grayscale writing. @@ -913,7 +970,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode420(Image pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default; @@ -935,6 +992,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 16) { @@ -945,7 +1004,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + // TODO: Try pushing this to the outer loop! + var currentRows = new RowOctet(pixelBuffer, y + yOff); + + pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows); cbPtr[i] = pixelConverter.Cb; crPtr[i] = pixelConverter.Cr; @@ -998,4 +1060,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.outputStream.Write(this.buffer, 0, 4); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs similarity index 100% rename from src/ImageSharp/Formats/Jpeg/JpegMetaData.cs rename to src/ImageSharp/Formats/Jpeg/JpegMetadata.cs diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index 7e8384dcff..da4c3f9ee0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -8,25 +9,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg internal static class JpegThrowHelper { /// - /// Cold path optimization for throwing 's. + /// Cold path optimization for throwing 's. /// /// The error message for the exception. [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); + + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotImplementedException(string errorMessage) + => throw new NotImplementedException(errorMessage); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadMarker(string marker, int length) => throw new ImageFormatException($"Marker {marker} has bad length {length}."); + public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadQuantizationTable() => throw new ImageFormatException("Bad Quantization Table index."); + public static void ThrowBadQuantizationTable() => throw new InvalidImageContentException("Bad Quantization Table index."); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadSampling() => throw new ImageFormatException("Bad sampling factor."); + public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new ImageFormatException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); + public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageDimensions(int width, int height) => throw new ImageFormatException($"Invalid image dimensions: {width}x{height}."); + public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs new file mode 100644 index 0000000000..53a9d2a35d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the jpeg format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index 66d04f39fd..1683519c2b 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats public int BitsPerPixel { get; } internal static PixelTypeInfo Create() - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => new PixelTypeInfo(Unsafe.SizeOf() * 8); } } diff --git a/src/ImageSharp/Formats/Png/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs new file mode 100644 index 0000000000..762a6c40cb --- /dev/null +++ b/src/ImageSharp/Formats/Png/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the png format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static PngMetadata GetPngMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PngFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index c75f9465af..7d8498ab75 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Png { @@ -10,12 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal readonly struct PngChunk { - public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0) + public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null) { this.Length = length; this.Type = type; this.Data = data; - this.Crc = crc; } /// @@ -38,20 +37,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// public IManagedByteBuffer Data { get; } - /// - /// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, - /// including the chunk type code and chunk data fields, but not including the length field. - /// The CRC is always present, even for chunks containing no data - /// - public uint Crc { get; } - /// /// Gets a value indicating whether the given chunk is critical to decoding /// public bool IsCritical => this.Type == PngChunkType.Header || this.Type == PngChunkType.Palette || - this.Type == PngChunkType.Data || - this.Type == PngChunkType.End; + this.Type == PngChunkType.Data; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs index e41b49066a..015f6984d4 100644 --- a/src/ImageSharp/Formats/Png/PngChunkType.cs +++ b/src/ImageSharp/Formats/Png/PngChunkType.cs @@ -73,6 +73,58 @@ namespace SixLabors.ImageSharp.Formats.Png /// either alpha values associated with palette entries (for indexed-color images) /// or a single transparent color (for grayscale and true color images). /// - Transparency = 0x74524E53U + Transparency = 0x74524E53U, + + /// + /// The tIME chunk gives the time of the last image modification (not the time of initial image creation). + /// + Time = 0x74494d45, + + /// + /// The bKGD chunk specifies a default background colour to present the image against. + /// If there is any other preferred background, either user-specified or part of a larger page (as in a browser), + /// the bKGD chunk should be ignored. + /// + Background = 0x624b4744, + + /// + /// The iCCP chunk contains a embedded color profile. If the iCCP chunk is present, + /// the image samples conform to the colour space represented by the embedded ICC profile as defined by the International Color Consortium. + /// + EmbeddedColorProfile = 0x69434350, + + /// + /// The sBIT chunk defines the original number of significant bits (which can be less than or equal to the sample depth). + /// This allows PNG decoders to recover the original data losslessly even if the data had a sample depth not directly supported by PNG. + /// + SignificantBits = 0x73424954, + + /// + /// If the sRGB chunk is present, the image samples conform to the sRGB colour space [IEC 61966-2-1] and should be displayed + /// using the specified rendering intent defined by the International Color Consortium. + /// + StandardRgbColourSpace = 0x73524742, + + /// + /// The hIST chunk gives the approximate usage frequency of each colour in the palette. + /// + Histogram = 0x68495354, + + /// + /// The sPLT chunk contains the suggested palette. + /// + SuggestedPalette = 0x73504c54, + + /// + /// The cHRM chunk may be used to specify the 1931 CIE x,y chromaticities of the red, + /// green, and blue display primaries used in the image, and the referenced white point. + /// + Chroma = 0x6348524d, + + /// + /// Malformed chunk named CgBI produced by apple, which is not conform to the specification. + /// Related issue is here https://github.com/SixLabors/ImageSharp/issues/410 + /// + ProprietaryApple = 0x43674249 } } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 632460ec43..247bb3c756 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; @@ -36,21 +37,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// public static readonly IEnumerable FileExtensions = new[] { "png" }; - /// - /// The header bytes identifying a Png. - /// - public static readonly byte[] HeaderBytes = - { - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - }; - /// /// The header bytes as a big-endian coded ulong. /// @@ -77,5 +63,20 @@ namespace SixLabors.ImageSharp.Formats.Png /// The minimum length of a keyword in a text chunk is 1 byte. /// public const int MinTextKeywordLength = 1; + + /// + /// Gets the header bytes identifying a Png. + /// + public static ReadOnlySpan HeaderBytes => new byte[] + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index eea9e54c01..eceb4e592f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -41,10 +42,23 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing image data. /// The decoded image. public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - return decoder.Decode(stream); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + PngThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + + // Not reachable, as the previous statement will throw a exception. + return null; + } } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 037f648f0a..a5bcff3b2b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -9,7 +9,7 @@ using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Advanced; + using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; @@ -17,7 +17,6 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png { @@ -107,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png private int currentRow = Adam7.FirstRow[0]; /// - /// The current number of bytes read in the current scanline + /// The current number of bytes read in the current scanline. /// private int currentRowBytesRead; @@ -133,23 +132,28 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.header.Width, this.header.Height); + /// /// Decodes the stream to the image. /// /// The pixel format. - /// The stream containing image data. + /// The stream containing image data. /// /// Thrown if the stream does not contain and end chunk. /// /// /// Thrown if the image is larger than the maximum allowable size. /// - /// The decoded image + /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var metadata = new ImageMetadata(); - PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + PngMetadata pngMetadata = metadata.GetPngMetadata(); this.currentStream = stream; this.currentStream.Skip(8); Image image = null; @@ -211,6 +215,9 @@ namespace SixLabors.ImageSharp.Formats.Png case PngChunkType.End: this.isEndChunkReached = true; break; + case PngChunkType.ProprietaryApple: + PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); + break; } } finally @@ -221,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (image is null) { - throw new ImageFormatException("PNG Image does not contain a data chunk"); + PngThrowHelper.ThrowNoData(); } return image; @@ -240,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Png public IImageInfo Identify(Stream stream) { var metadata = new ImageMetadata(); - PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + PngMetadata pngMetadata = metadata.GetPngMetadata(); this.currentStream = stream; this.currentStream.Skip(8); try @@ -265,6 +272,21 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.Text: this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.CompressedText: + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.InternationalText: + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.Exif: + if (!this.ignoreMetadata) + { + var exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metadata.ExifProfile = new ExifProfile(exifData); + } + break; case PngChunkType.End: this.isEndChunkReached = true; @@ -285,7 +307,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.Width == 0 && this.header.Height == 0) { - throw new ImageFormatException("PNG Image does not contain a header chunk"); + PngThrowHelper.ThrowNoHeader(); } return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); @@ -374,9 +396,14 @@ namespace SixLabors.ImageSharp.Formats.Png /// The metadata information for the image /// The image that we will populate private void InitializeImage(ImageMetadata metadata, out Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); + image = Image.CreateUninitialized( + this.configuration, + this.header.Width, + this.header.Height, + metadata); + this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerSample = 1; @@ -407,7 +434,8 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.RgbWithAlpha: return this.header.BitDepth * 4; default: - throw new NotSupportedException("Unsupported PNG color type"); + PngThrowHelper.ThrowNotSupportedColor(); + return -1; } } @@ -466,7 +494,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel data. /// The png metadata private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) { @@ -492,7 +520,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image to decode to. /// The png metadata private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { while (this.currentRow < this.header.Height) { @@ -528,7 +556,8 @@ namespace SixLabors.ImageSharp.Formats.Png break; default: - throw new ImageFormatException("Unknown filter type."); + PngThrowHelper.ThrowUnknownFilter(); + break; } this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); @@ -547,7 +576,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The current image. /// The png metadata. private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int pass = 0; int width = this.header.Width; @@ -601,7 +630,8 @@ namespace SixLabors.ImageSharp.Formats.Png break; default: - throw new ImageFormatException("Unknown filter type."); + PngThrowHelper.ThrowUnknownFilter(); + break; } Span rowSpan = image.GetPixelRowSpan(this.currentRow); @@ -635,7 +665,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image /// The png metadata. private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); @@ -655,8 +685,8 @@ namespace SixLabors.ImageSharp.Formats.Png scanlineSpan, rowSpan, pngMetadata.HasTransparency, - pngMetadata.TransparentGray16.GetValueOrDefault(), - pngMetadata.TransparentGray8.GetValueOrDefault()); + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); break; @@ -719,7 +749,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); @@ -739,8 +769,8 @@ namespace SixLabors.ImageSharp.Formats.Png pixelOffset, increment, pngMetadata.HasTransparency, - pngMetadata.TransparentGray16.GetValueOrDefault(), - pngMetadata.TransparentGray8.GetValueOrDefault()); + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); break; @@ -834,11 +864,11 @@ namespace SixLabors.ImageSharp.Formats.Png { if (this.header.BitDepth == 16) { - pngMetadata.TransparentGray16 = new Gray16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); + pngMetadata.TransparentL16 = new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); } else { - pngMetadata.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0)); + pngMetadata.TransparentL8 = new L8(ReadByteLittleEndian(alpha, 0)); } pngMetadata.HasTransparency = true; @@ -1027,7 +1057,7 @@ namespace SixLabors.ImageSharp.Formats.Png var uncompressedBytes = new List(); - // Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here. + // Note: this uses a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here. int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); while (bytesRead != 0) { @@ -1094,10 +1124,7 @@ namespace SixLabors.ImageSharp.Formats.Png while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position)) { - // Not a valid chunk so we skip back all but one of the four bytes we have just read. - // That lets us read one byte at a time until we reach a known chunk. - this.currentStream.Position -= 3; - + // Not a valid chunk so try again until we reach a known chunk. if (!this.TryReadChunkLength(out length)) { chunk = default; @@ -1119,13 +1146,9 @@ namespace SixLabors.ImageSharp.Formats.Png chunk = new PngChunk( length: length, type: type, - data: this.ReadChunkData(length), - crc: this.ReadChunkCrc()); + data: this.ReadChunkData(length)); - if (chunk.IsCritical) - { - this.ValidateChunk(chunk); - } + this.ValidateChunk(chunk); return true; } @@ -1136,39 +1159,45 @@ namespace SixLabors.ImageSharp.Formats.Png /// The . private void ValidateChunk(in PngChunk chunk) { - Span chunkType = stackalloc byte[4]; - - BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); - - this.crc.Reset(); - this.crc.Update(chunkType); - this.crc.Update(chunk.Data.GetSpan()); + uint crc = this.ReadChunkCrc(); - if (this.crc.Value != chunk.Crc) + if (chunk.IsCritical) { - string chunkTypeName = Encoding.ASCII.GetString(chunkType); + Span chunkType = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); + + this.crc.Reset(); + this.crc.Update(chunkType); + this.crc.Update(chunk.Data.GetSpan()); - throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); + if (this.crc.Value != crc) + { + string chunkTypeName = Encoding.ASCII.GetString(chunkType); + PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); + } } } /// /// Reads the cycle redundancy chunk from the data. /// - /// - /// Thrown if the input stream is not valid or corrupt. - /// + [MethodImpl(InliningOptions.ShortMethod)] private uint ReadChunkCrc() { - return this.currentStream.Read(this.buffer, 0, 4) == 4 - ? BinaryPrimitives.ReadUInt32BigEndian(this.buffer) - : throw new ImageFormatException("Image stream is not valid!"); + uint crc = 0; + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + } + + return crc; } /// /// Skips the chunk data and the cycle redundancy chunk read from the data. /// /// The image format chunk. + [MethodImpl(InliningOptions.ShortMethod)] private void SkipChunkDataAndCrc(in PngChunk chunk) { this.currentStream.Skip(chunk.Length); @@ -1179,6 +1208,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Reads the chunk data from the stream. /// /// The length of the chunk data to read. + [MethodImpl(InliningOptions.ShortMethod)] private IManagedByteBuffer ReadChunkData(int length) { // We rent the buffer here to return it afterwards in Decode() @@ -1195,11 +1225,20 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Thrown if the input stream is not valid. /// + [MethodImpl(InliningOptions.ShortMethod)] private PngChunkType ReadChunkType() { - return this.currentStream.Read(this.buffer, 0, 4) == 4 - ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) - : throw new ImageFormatException("Invalid PNG data."); + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + } + else + { + PngThrowHelper.ThrowInvalidChunkType(); + + // The IDE cannot detect the throw here. + return default; + } } /// @@ -1208,6 +1247,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Whether the length was read. /// + [MethodImpl(InliningOptions.ShortMethod)] private bool TryReadChunkLength(out int result) { if (this.currentStream.Read(this.buffer, 0, 4) == 4) diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 16bd538c31..0f83d3d42e 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index a3cc1d0187..47a377e67a 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -5,10 +5,8 @@ using System; using System.Buffers; using System.Buffers.Binary; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; @@ -16,8 +14,6 @@ using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Png { @@ -136,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -145,29 +141,31 @@ namespace SixLabors.ImageSharp.Formats.Png this.height = image.Height; ImageMetadata metadata = image.Metadata; - PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); - PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); - - IQuantizedFrame quantized; + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); + IndexedImageFrame quantized; if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.MakeTransparentBlack) == PngOptimizeMethod.MakeTransparentBlack) { using (Image tempImage = image.Clone()) { - Span span = tempImage.GetPixelSpan(); - for (int i = 0; i < span.Length; i++) + for (int y = 0; y < image.Height; y++) { - Rgba32 rgba32 = default; - span[i].ToRgba32(ref rgba32); - - if (rgba32.A == 0) + Span span = tempImage.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) { - rgba32.R = 0; - rgba32.G = 0; - rgba32.B = 0; - } + Rgba32 rgba32 = default; + span[x].ToRgba32(ref rgba32); - span[i].FromRgba32(rgba32); + if (rgba32.A == 0) + { + rgba32.R = 0; + rgba32.G = 0; + rgba32.B = 0; + } + + span[x].FromRgba32(rgba32); + } } quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, tempImage); @@ -180,9 +178,15 @@ namespace SixLabors.ImageSharp.Formats.Png this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); } - stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); + stream.Write(PngConstants.HeaderBytes); this.WriteHeaderChunk(stream); + + if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressGammaChunk) != PngOptimizeMethod.SuppressGammaChunk) + { + this.WriteGammaChunk(stream); + } + this.WritePaletteChunk(stream, quantized); this.WriteTransparencyChunk(stream, pngMetadata); @@ -191,11 +195,6 @@ namespace SixLabors.ImageSharp.Formats.Png this.WritePhysicalChunk(stream, metadata); } - if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressGammaChunk) != PngOptimizeMethod.SuppressGammaChunk) - { - this.WriteGammaChunk(stream); - } - if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressExifChunk) != PngOptimizeMethod.SuppressExifChunk) { this.WriteExifChunk(stream, metadata); @@ -235,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The image row span. private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); Span rawScanlineSpan = this.currentScanline.GetSpan(); @@ -246,16 +245,16 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.use16Bit) { // 16 bit grayscale - using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) { - Span luminanceSpan = luminanceBuffer.GetSpan(); - ref Gray16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan); - PixelOperations.Instance.ToGray16(this.configuration, rowSpan, luminanceSpan); + Span luminanceSpan = luminanceBuffer.GetSpan(); + ref L16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan); + PixelOperations.Instance.ToL16(this.configuration, rowSpan, luminanceSpan); // Can't map directly to byte array as it's big-endian. for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2) { - Gray16 luminance = Unsafe.Add(ref luminanceRef, x); + L16 luminance = Unsafe.Add(ref luminanceRef, x); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue); } } @@ -265,7 +264,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth == 8) { // 8 bit grayscale - PixelOperations.Instance.ToGray8Bytes( + PixelOperations.Instance.ToL8Bytes( this.configuration, rowSpan, rawScanlineSpan, @@ -282,7 +281,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span tempSpan = temp.GetSpan(); // We need to first create an array of luminance bytes then scale them down to the correct bit depth. - PixelOperations.Instance.ToGray8Bytes( + PixelOperations.Instance.ToL8Bytes( this.configuration, rowSpan, tempSpan, @@ -336,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The row span. private void CollectTPixelBytes(ReadOnlySpan rowSpan) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span rawScanlineSpan = this.currentScanline.GetSpan(); @@ -419,8 +418,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The row span. /// The quantized pixels. Can be null. /// The row. - private void CollectPixelBytes(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row) - where TPixel : struct, IPixel + private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel { switch (this.options.ColorType) { @@ -428,12 +427,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); } else { - int stride = this.currentScanline.Length(); - quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); + quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; @@ -488,8 +486,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row) - where TPixel : struct, IPixel + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); return this.FilterPixelBytes(); @@ -590,68 +588,65 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Writes the palette chunk to the stream. + /// Should be written before the first IDAT chunk. /// /// The pixel format. /// The containing image data. /// The quantized frame. - private void WritePaletteChunk(Stream stream, IQuantizedFrame quantized) - where TPixel : struct, IPixel + private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { - if (quantized == null) + if (quantized is null) { return; } // Grab the palette and write it to the stream. ReadOnlySpan palette = quantized.Palette.Span; - int paletteLength = Math.Min(palette.Length, 256); - int colorTableLength = paletteLength * 3; - bool anyAlpha = false; + int paletteLength = palette.Length; + int colorTableLength = paletteLength * Unsafe.SizeOf(); + bool hasAlpha = false; - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) - { - ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); - ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - ReadOnlySpan quantizedSpan = quantized.GetPixelSpan(); + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); + using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); - Rgba32 rgba = default; + ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - for (int i = 0; i < paletteLength; i++) - { - if (quantizedSpan.IndexOf((byte)i) > -1) - { - int offset = i * 3; - palette[i].ToRgba32(ref rgba); - - byte alpha = rgba.A; - - Unsafe.Add(ref colorTableRef, offset) = rgba.R; - Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G; - Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B; + // Bulk convert our palette to RGBA to allow assignment to tables. + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteLength); + Span rgbaPaletteSpan = rgbaOwner.GetSpan(); + PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan); + ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan); - if (alpha > this.options.Threshold) - { - alpha = byte.MaxValue; - } + // Loop, assign, and extract alpha values from the palette. + for (int i = 0; i < paletteLength; i++) + { + Rgba32 rgba = Unsafe.Add(ref rgbaPaletteRef, i); + byte alpha = rgba.A; - anyAlpha = anyAlpha || alpha < byte.MaxValue; - Unsafe.Add(ref alphaTableRef, i) = alpha; - } + Unsafe.Add(ref colorTableRef, i) = rgba.Rgb; + if (alpha > this.options.Threshold) + { + alpha = byte.MaxValue; } - this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + hasAlpha = hasAlpha || alpha < byte.MaxValue; + Unsafe.Add(ref alphaTableRef, i) = alpha; + } - // Write the transparency data - if (anyAlpha) - { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); - } + this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + + // Write the transparency data + if (hasAlpha) + { + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); } } /// /// Writes the physical dimension information to the stream. + /// Should be written before IDAT chunk. /// /// The containing image data. /// The image metadata. @@ -669,11 +664,13 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image metadata. private void WriteExifChunk(Stream stream, ImageMetadata meta) { - if (meta.ExifProfile?.Values.Count > 0) + if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0) { - meta.SyncProfiles(); - this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); + return; } + + meta.SyncProfiles(); + this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); } /// @@ -685,10 +682,21 @@ namespace SixLabors.ImageSharp.Formats.Png private void WriteTextChunks(Stream stream, PngMetadata meta) { const int MaxLatinCode = 255; - foreach (PngTextData textData in meta.TextData) + for (int i = 0; i < meta.TextData.Count; i++) { - bool hasUnicodeCharacters = textData.Value.Any(c => c > MaxLatinCode); - if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword))) + PngTextData textData = meta.TextData[i]; + bool hasUnicodeCharacters = false; + foreach (var c in textData.Value) + { + if (c > MaxLatinCode) + { + hasUnicodeCharacters = true; + break; + } + } + + if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || + !string.IsNullOrWhiteSpace(textData.TranslatedKeyword))) { // Write iTXt chunk. byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword); @@ -699,7 +707,8 @@ namespace SixLabors.ImageSharp.Formats.Png byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); - Span outputBytes = new byte[keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5]; + Span outputBytes = new byte[keywordBytes.Length + textBytes.Length + + translatedKeyword.Length + languageTag.Length + 5]; keywordBytes.CopyTo(outputBytes); if (textData.Value.Length > this.options.TextCompressionThreshold) { @@ -719,7 +728,8 @@ namespace SixLabors.ImageSharp.Formats.Png if (textData.Value.Length > this.options.TextCompressionThreshold) { // Write zTXt chunk. - byte[] compressedData = this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value)); + byte[] compressedData = + this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value)); Span outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2]; PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2)); @@ -730,7 +740,8 @@ namespace SixLabors.ImageSharp.Formats.Png // Write tEXt chunk. Span outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1]; PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes.Slice(textData.Keyword.Length + 1)); + PngConstants.Encoding.GetBytes(textData.Value) + .CopyTo(outputBytes.Slice(textData.Keyword.Length + 1)); this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray()); } } @@ -746,7 +757,7 @@ namespace SixLabors.ImageSharp.Formats.Png { using (var memoryStream = new MemoryStream()) { - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) + using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { deflateStream.Write(textBytes); } @@ -757,6 +768,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Writes the gamma information to the stream. + /// Should be written before PLTE and IDAT chunk. /// /// The containing image data. private void WriteGammaChunk(Stream stream) @@ -774,6 +786,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Writes the transparency chunk to the stream. + /// Should be written after PLTE and before IDAT. /// /// The containing image data. /// The image metadata. @@ -808,15 +821,15 @@ namespace SixLabors.ImageSharp.Formats.Png } else if (pngMetadata.ColorType == PngColorType.Grayscale) { - if (pngMetadata.TransparentGray16.HasValue && this.use16Bit) + if (pngMetadata.TransparentL16.HasValue && this.use16Bit) { - BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentGray16.Value.PackedValue); + BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue); this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } - else if (pngMetadata.TransparentGray8.HasValue) + else if (pngMetadata.TransparentL8.HasValue) { alpha.Clear(); - alpha[1] = pngMetadata.TransparentGray8.Value.PackedValue; + alpha[1] = pngMetadata.TransparentL8.Value.PackedValue; this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } } @@ -829,15 +842,15 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image. /// The quantized pixel data. Can be null. /// The stream. - private void WriteDataChunks(ImageFrame pixels, IQuantizedFrame quantized, Stream stream) - where TPixel : struct, IPixel + private void WriteDataChunks(ImageFrame pixels, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel { byte[] buffer; int bufferLength; using (var memoryStream = new MemoryStream()) { - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) + using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { if (this.options.InterlaceMethod == PngInterlaceMode.Adam7) { @@ -927,8 +940,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixels. /// The quantized pixels span. /// The deflate stream. - private void EncodePixels(ImageFrame pixels, IQuantizedFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : struct, IPixel + private void EncodePixels(ImageFrame pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); int resultLength = bytesPerScanline + 1; @@ -952,7 +965,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixels. /// The deflate stream. private void EncodeAdam7Pixels(ImageFrame pixels, ZlibDeflateStream deflateStream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int width = pixels.Width; int height = pixels.Height; @@ -1006,8 +1019,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The quantized. /// The deflate stream. - private void EncodeAdam7IndexedPixels(IQuantizedFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : struct, IPixel + private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel { int width = quantized.Width; int height = quantized.Height; @@ -1033,7 +1046,7 @@ namespace SixLabors.ImageSharp.Formats.Png row += Adam7.RowIncrement[pass]) { // collect data - ReadOnlySpan srcRow = quantized.GetRowSpan(row); + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index e3f2948864..3f490ca6f8 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -14,23 +14,28 @@ namespace SixLabors.ImageSharp.Formats.Png internal static class PngEncoderOptionsHelpers { /// - /// Adjusts the options. + /// Adjusts the options based upon the given metadata. /// /// The options. /// The PNG metadata. /// if set to true [use16 bit]. /// The bytes per pixel. - public static void AdjustOptions( + public static void AdjustOptions( PngEncoderOptions options, PngMetadata pngMetadata, out bool use16Bit, out int bytesPerPixel) + where TPixel : unmanaged, IPixel { // Always take the encoder options over the metadata values. - options.Gamma = options.Gamma ?? pngMetadata.Gamma; - options.ColorType = options.ColorType ?? pngMetadata.ColorType; - options.BitDepth = options.BitDepth ?? pngMetadata.BitDepth; - options.InterlaceMethod = options.InterlaceMethod ?? pngMetadata.InterlaceMethod; + options.Gamma ??= pngMetadata.Gamma; + + // Use options, then check metadata, if nothing set there then we suggest + // a sensible default based upon the pixel format. + options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType(); + options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth(); + + options.InterlaceMethod ??= pngMetadata.InterlaceMethod; use16Bit = options.BitDepth == PngBitDepth.Bit16; bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); @@ -48,10 +53,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The options. /// The image. - public static IQuantizedFrame CreateQuantizedFrame( + public static IndexedImageFrame CreateQuantizedFrame( PngEncoderOptions options, Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (options.ColorType != PngColorType.Palette) { @@ -67,13 +72,15 @@ namespace SixLabors.ImageSharp.Formats.Png // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { - options.Quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits)); + var maxColors = ImageMaths.GetColorCountForBitDepth(bits); + options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); } // Create quantized frame returning the palette and set the bit depth. using (IFrameQuantizer frameQuantizer = options.Quantizer.CreateFrameQuantizer(image.GetConfiguration())) { - return frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + ImageFrame frame = image.Frames.RootFrame; + return frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } @@ -87,8 +94,8 @@ namespace SixLabors.ImageSharp.Formats.Png public static byte CalculateBitDepth( PngEncoderOptions options, Image image, - IQuantizedFrame quantizedFrame) - where TPixel : struct, IPixel + IndexedImageFrame quantizedFrame) + where TPixel : unmanaged, IPixel { byte bitDepth; if (options.ColorType == PngColorType.Palette) @@ -129,24 +136,68 @@ namespace SixLabors.ImageSharp.Formats.Png /// Bytes per pixel. private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit) { - switch (pngColorType) + return pngColorType switch { - case PngColorType.Grayscale: - return use16Bit ? 2 : 1; + PngColorType.Grayscale => use16Bit ? 2 : 1, + PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2, + PngColorType.Palette => 1, + PngColorType.Rgb => use16Bit ? 6 : 3, - case PngColorType.GrayscaleWithAlpha: - return use16Bit ? 4 : 2; - - case PngColorType.Palette: - return 1; + // PngColorType.RgbWithAlpha + _ => use16Bit ? 8 : 4, + }; + } - case PngColorType.Rgb: - return use16Bit ? 6 : 3; + /// + /// Returns a suggested for the given + /// This is not exhaustive but covers many common pixel formats. + /// + private static PngColorType SuggestColorType() + where TPixel : unmanaged, IPixel + { + return typeof(TPixel) switch + { + Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(Bgr24) => PngColorType.Rgb, + Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(L8) => PngColorType.Grayscale, + Type t when t == typeof(L16) => PngColorType.Grayscale, + Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(Rgb24) => PngColorType.Rgb, + Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(Rgb48) => PngColorType.Rgb, + Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha, + Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha, + _ => PngColorType.RgbWithAlpha + }; + } - // PngColorType.RgbWithAlpha - default: - return use16Bit ? 8 : 4; - } + /// + /// Returns a suggested for the given + /// This is not exhaustive but covers many common pixel formats. + /// + private static PngBitDepth SuggestBitDepth() + where TPixel : unmanaged, IPixel + { + return typeof(TPixel) switch + { + Type t when t == typeof(A8) => PngBitDepth.Bit8, + Type t when t == typeof(Argb32) => PngBitDepth.Bit8, + Type t when t == typeof(Bgr24) => PngBitDepth.Bit8, + Type t when t == typeof(Bgra32) => PngBitDepth.Bit8, + Type t when t == typeof(L8) => PngBitDepth.Bit8, + Type t when t == typeof(L16) => PngBitDepth.Bit16, + Type t when t == typeof(La16) => PngBitDepth.Bit8, + Type t when t == typeof(La32) => PngBitDepth.Bit16, + Type t when t == typeof(Rgb24) => PngBitDepth.Bit8, + Type t when t == typeof(Rgba32) => PngBitDepth.Bit8, + Type t when t == typeof(Rgb48) => PngBitDepth.Bit16, + Type t when t == typeof(Rgba64) => PngBitDepth.Bit16, + Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16, + _ => PngBitDepth.Bit8 + }; } } } diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs deleted file mode 100644 index ec8779a59a..0000000000 --- a/src/ImageSharp/Formats/Png/PngMetaData.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Png -{ - /// - /// Provides Png specific metadata information for the image. - /// - public class PngMetadata : IDeepCloneable - { - /// - /// Initializes a new instance of the class. - /// - public PngMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private PngMetadata(PngMetadata other) - { - this.BitDepth = other.BitDepth; - this.ColorType = other.ColorType; - this.Gamma = other.Gamma; - this.InterlaceMethod = other.InterlaceMethod; - this.HasTransparency = other.HasTransparency; - this.TransparentGray8 = other.TransparentGray8; - this.TransparentGray16 = other.TransparentGray16; - this.TransparentRgb24 = other.TransparentRgb24; - this.TransparentRgb48 = other.TransparentRgb48; - - for (int i = 0; i < other.TextData.Count; i++) - { - this.TextData.Add(other.TextData[i]); - } - } - - /// - /// Gets or sets the number of bits per sample or per palette index (not per pixel). - /// Not all values are allowed for all values. - /// - public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8; - - /// - /// Gets or sets the color type. - /// - public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image. - /// - public PngInterlaceMode? InterlaceMethod { get; set; } = PngInterlaceMode.None; - - /// - /// Gets or sets the gamma value for the image. - /// - public float Gamma { get; set; } - - /// - /// Gets or sets the Rgb24 transparent color. - /// This represents any color in an 8 bit Rgb24 encoded png that should be transparent. - /// - public Rgb24? TransparentRgb24 { get; set; } - - /// - /// Gets or sets the Rgb48 transparent color. - /// This represents any color in a 16 bit Rgb24 encoded png that should be transparent. - /// - public Rgb48? TransparentRgb48 { get; set; } - - /// - /// Gets or sets the 8 bit grayscale transparent color. - /// This represents any color in an 8 bit grayscale encoded png that should be transparent. - /// - public Gray8? TransparentGray8 { get; set; } - - /// - /// Gets or sets the 16 bit grayscale transparent color. - /// This represents any color in a 16 bit grayscale encoded png that should be transparent. - /// - public Gray16? TransparentGray16 { get; set; } - - /// - /// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded. - /// - public bool HasTransparency { get; set; } - - /// - /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks. - /// Used for conveying textual information associated with the image. - /// - public IList TextData { get; set; } = new List(); - - /// - /// Gets the list of png text properties for storing meta information about this image. - /// - public IList PngTextProperties { get; } = new List(); - - /// - public IDeepCloneable DeepClone() => new PngMetadata(this); - - internal bool TryGetPngTextProperty(string keyword, out PngTextData result) - { - for (int i = 0; i < this.TextData.Count; i++) - { - if (this.TextData[i].Keyword == keyword) - { - result = this.TextData[i]; - - return true; - } - } - - result = default; - - return false; - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs new file mode 100644 index 0000000000..1e4567548b --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Provides Png specific metadata information for the image. + /// + public class PngMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public PngMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PngMetadata(PngMetadata other) + { + this.BitDepth = other.BitDepth; + this.ColorType = other.ColorType; + this.Gamma = other.Gamma; + this.InterlaceMethod = other.InterlaceMethod; + this.HasTransparency = other.HasTransparency; + this.TransparentL8 = other.TransparentL8; + this.TransparentL16 = other.TransparentL16; + this.TransparentRgb24 = other.TransparentRgb24; + this.TransparentRgb48 = other.TransparentRgb48; + + for (int i = 0; i < other.TextData.Count; i++) + { + this.TextData.Add(other.TextData[i]); + } + } + + /// + /// Gets or sets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. + /// + public PngBitDepth? BitDepth { get; set; } + + /// + /// Gets or sets the color type. + /// + public PngColorType? ColorType { get; set; } + + /// + /// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image. + /// + public PngInterlaceMode? InterlaceMethod { get; set; } = PngInterlaceMode.None; + + /// + /// Gets or sets the gamma value for the image. + /// + public float Gamma { get; set; } + + /// + /// Gets or sets the Rgb24 transparent color. + /// This represents any color in an 8 bit Rgb24 encoded png that should be transparent. + /// + public Rgb24? TransparentRgb24 { get; set; } + + /// + /// Gets or sets the Rgb48 transparent color. + /// This represents any color in a 16 bit Rgb24 encoded png that should be transparent. + /// + public Rgb48? TransparentRgb48 { get; set; } + + /// + /// Gets or sets the 8 bit grayscale transparent color. + /// This represents any color in an 8 bit grayscale encoded png that should be transparent. + /// + public L8? TransparentL8 { get; set; } + + /// + /// Gets or sets the 16 bit grayscale transparent color. + /// This represents any color in a 16 bit grayscale encoded png that should be transparent. + /// + public L16? TransparentL16 { get; set; } + + /// + /// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded. + /// + public bool HasTransparency { get; set; } + + /// + /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks. + /// Used for conveying textual information associated with the image. + /// + public IList TextData { get; set; } = new List(); + + /// + public IDeepCloneable DeepClone() => new PngMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index c23694951d..cf365c8b92 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan scanlineSpan, Span rowSpan, bool hasTrans, - Gray16 luminance16Trans, - Gray8 luminanceTrans) - where TPixel : struct, IPixel + L16 luminance16Trans, + L8 luminanceTrans) + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int x = 0, o = 0; x < header.Width; x++, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - pixel.FromGray16(new Gray16(luminance)); + pixel.FromL16(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int x = 0; x < header.Width; x++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); - pixel.FromGray8(new Gray8(luminance)); + pixel.FromL8(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -55,32 +55,28 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = default; for (int x = 0, o = 0; x < header.Width; x++, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; + source.L = luminance; + source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { + La16 source = default; byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); - Rgba32 rgba32 = default; for (int x = 0; x < header.Width; x++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; + source.L = luminance; + source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -93,9 +89,9 @@ namespace SixLabors.ImageSharp.Formats.Png int pixelOffset, int increment, bool hasTrans, - Gray16 luminance16Trans, - Gray8 luminanceTrans) - where TPixel : struct, IPixel + L16 luminance16Trans, + L8 luminanceTrans) + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -109,7 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - pixel.FromGray16(new Gray16(luminance)); + pixel.FromL16(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -118,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); - pixel.FromGray8(new Gray8(luminance)); + pixel.FromL8(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -128,32 +124,28 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = default; for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; + source.L = luminance; + source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { + La16 source = default; byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); - Rgba32 rgba32 = default; for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; + source.L = luminance; + source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -165,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -173,35 +165,26 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = default; for (int x = 0, o = 0; x < header.Width; x++, o += 4) { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = alpha; + source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - Rgba32 rgba32 = default; + La16 source = default; for (int x = 0; x < header.Width; x++) { int offset = x * bytesPerPixel; - byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); - byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = alpha; + source.L = Unsafe.Add(ref scanlineSpanRef, offset); + source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } @@ -215,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -223,34 +206,26 @@ namespace SixLabors.ImageSharp.Formats.Png if (header.BitDepth == 16) { - Rgba64 rgba64 = default; + La32 source = default; for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 4) { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = alpha; + source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - pixel.FromRgba64(rgba64); + pixel.FromLa32(source); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - Rgba32 rgba32 = default; int offset = 0; + La16 source = default; for (int x = pixelOffset; x < header.Width; x += increment) { - byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); - byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = alpha; + source.L = Unsafe.Add(ref scanlineSpanRef, offset); + source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - pixel.FromRgba32(rgba32); + pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; offset += bytesPerPixel; } @@ -263,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, ReadOnlySpan palette, byte[] paletteAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -309,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, ReadOnlySpan palette, byte[] paletteAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -356,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, Rgb48 rgb48Trans, Rgb24 rgb24Trans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -403,12 +378,12 @@ namespace SixLabors.ImageSharp.Formats.Png } else { + Rgba32 rgba32 = default; ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); for (int x = 0; x < header.Width; x++) { ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); - Rgba32 rgba32 = default; rgba32.Rgb = rgb24; rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; @@ -429,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, Rgb48 rgb48Trans, Rgb24 rgb24Trans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -507,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -540,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -576,4 +551,4 @@ namespace SixLabors.ImageSharp.Formats.Png } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs new file mode 100644 index 0000000000..a72a4a0d88 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Cold path optimizations for throwing png format based exceptions. + /// + internal static class PngThrowHelper + { + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) + => throw new InvalidImageContentException(errorMessage, innerException); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkType(string message) => throw new InvalidImageContentException(message); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type."); + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs index a06983b9ed..c4dc82a4dc 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -1,8 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -112,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(ReadOnlySpan data) { - // (By Per Bothner) + ref byte dataRef = ref MemoryMarshal.GetReference(data); uint s1 = this.checksum & 0xFFFF; uint s2 = this.checksum >> 16; @@ -133,8 +134,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib count -= n; while (--n >= 0) { - s1 = s1 + (uint)(data[offset++] & 0xff); - s2 = s2 + s1; + s1 += Unsafe.Add(ref dataRef, offset++); + s2 += s1; } s1 %= Base; @@ -144,4 +145,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.checksum = (s2 << 16) | s1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs index d1588c384f..77355e908c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs @@ -1,8 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -141,9 +142,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.crc ^= CrcSeed; + ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); for (int i = 0; i < data.Length; i++) { - this.crc = CrcTable[(this.crc ^ data[i]) & 0xFF] ^ (this.crc >> 8); + this.crc = Unsafe.Add(ref crcTableRef, (int)((this.crc ^ data[i]) & 0xFF)) ^ (this.crc >> 8); } this.crc ^= CrcSeed; diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs new file mode 100644 index 0000000000..5f62b13c7f --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + internal static class DeflateThrowHelper + { + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNull(string name) => throw new ArgumentNullException(name); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input."); + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs new file mode 100644 index 0000000000..2083edab1c --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs @@ -0,0 +1,293 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// This class compresses input with the deflate algorithm described in RFC 1951. + /// It has several compression levels and three different strategies described below. + /// + internal sealed class Deflater : IDisposable + { + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + public const int BestCompression = 9; + + /// + /// The worst but fastest compression level. + /// + public const int BestSpeed = 1; + + /// + /// The default compression level. + /// + public const int DefaultCompression = -1; + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + public const int NoCompression = 0; + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + public const int Deflated = 8; + + /// + /// Compression level. + /// + private int level; + + /// + /// The current state. + /// + private int state; + + private DeflaterEngine engine; + private bool isDisposed; + + private const int IsFlushing = 0x04; + private const int IsFinishing = 0x08; + private const int BusyState = 0x10; + private const int FlushingState = 0x14; + private const int FinishingState = 0x1c; + private const int FinishedState = 0x1e; + private const int ClosedState = 0x7f; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The compression level, a value between NoCompression and BestCompression. + /// + /// if level is out of range. + public Deflater(MemoryAllocator memoryAllocator, int level) + { + if (level == DefaultCompression) + { + level = 6; + } + else if (level < NoCompression || level > BestCompression) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + // TODO: Possibly provide DeflateStrategy as an option. + this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default); + + this.SetLevel(level); + this.Reset(); + } + + /// + /// Compression Level as an enum for safer use + /// + public enum CompressionLevel + { + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + BestCompression = Deflater.BestCompression, + + /// + /// The worst but fastest compression level. + /// + BestSpeed = Deflater.BestSpeed, + + /// + /// The default compression level. + /// + DefaultCompression = Deflater.DefaultCompression, + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + NoCompression = Deflater.NoCompression, + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + Deflated = Deflater.Deflated + } + + /// + /// Gets a value indicating whetherthe stream was finished and no more output bytes + /// are available. + /// + public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed; + + /// + /// Gets a value indicating whether the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method can also return true when the stream + /// was finished. + /// + public bool IsNeedingInput => this.engine.NeedsInput(); + + /// + /// Resets the deflater. The deflater acts afterwards as if it was + /// just created with the same compression level and strategy as it + /// had before. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.state = BusyState; + this.engine.Pending.Reset(); + this.engine.Reset(); + } + + /// + /// Flushes the current input block. Further calls to Deflate() will + /// produce enough output to inflate everything in the current input + /// block. It is used by DeflaterOutputStream to implement Flush(). + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Flush() => this.state |= IsFlushing; + + /// + /// Finishes the deflater with the current input block. It is an error + /// to give more input after this method was called. This method must + /// be called to force all bytes to be flushed. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Finish() => this.state |= IsFlushing | IsFinishing; + + /// + /// Sets the data which should be compressed next. This should be + /// only called when needsInput indicates that more input is needed. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// + /// The buffer containing the input data. + /// The start of the data. + /// The number of data bytes of input. + /// + /// if the buffer was finished or if previous input is still pending. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void SetInput(byte[] input, int offset, int count) + { + if ((this.state & IsFinishing) != 0) + { + DeflateThrowHelper.ThrowAlreadyFinished(); + } + + this.engine.SetInput(input, offset, count); + } + + /// + /// Sets the compression level. There is no guarantee of the exact + /// position of the change, but if you call this when needsInput is + /// true the change of compression level will occur somewhere near + /// before the end of the so far given input. + /// + /// + /// the new compression level. + /// + public void SetLevel(int level) + { + if (level == DefaultCompression) + { + level = 6; + } + else if (level < NoCompression || level > BestCompression) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + if (this.level != level) + { + this.level = level; + this.engine.SetLevel(level); + } + } + + /// + /// Deflates the current input block to the given array. + /// + /// Buffer to store the compressed data. + /// Offset into the output array. + /// The maximum number of bytes that may be stored. + /// + /// The number of compressed bytes added to the output, or 0 if either + /// or returns true or length is zero. + /// + public int Deflate(byte[] output, int offset, int length) + { + int origLength = length; + + if (this.state == ClosedState) + { + DeflateThrowHelper.ThrowAlreadyClosed(); + } + + while (true) + { + int count = this.engine.Pending.Flush(output, offset, length); + offset += count; + length -= count; + + if (length == 0 || this.state == FinishedState) + { + break; + } + + if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0)) + { + switch (this.state) + { + case BusyState: + // We need more input now + return origLength - length; + + case FlushingState: + if (this.level != NoCompression) + { + // We have to supply some lookahead. 8 bit lookahead + // is needed by the zlib inflater, and we must fill + // the next byte, so that all bits are flushed. + int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7); + while (neededbits > 0) + { + // Write a static tree block consisting solely of an EOF: + this.engine.Pending.WriteBits(2, 10); + neededbits -= 10; + } + } + + this.state = BusyState; + break; + + case FinishingState: + this.engine.Pending.AlignToByte(); + this.state = FinishedState; + break; + } + } + } + + return origLength - length; + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.engine.Dispose(); + this.engine = null; + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs new file mode 100644 index 0000000000..a3a87a32f8 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// +using System; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// This class contains constants used for deflation. + /// + internal static class DeflaterConstants + { + /// + /// Set to true to enable debugging + /// + public const bool DEBUGGING = false; + + /// + /// Written to Zip file to identify a stored block + /// + public const int STORED_BLOCK = 0; + + /// + /// Identifies static tree in Zip file + /// + public const int STATIC_TREES = 1; + + /// + /// Identifies dynamic tree in Zip file + /// + public const int DYN_TREES = 2; + + /// + /// Header flag indicating a preset dictionary for deflation + /// + public const int PRESET_DICT = 0x20; + + /// + /// Sets internal buffer sizes for Huffman encoding + /// + public const int DEFAULT_MEM_LEVEL = 8; + + /// + /// Internal compression engine constant + /// + public const int MAX_MATCH = 258; + + /// + /// Internal compression engine constant + /// + public const int MIN_MATCH = 3; + + /// + /// Internal compression engine constant + /// + public const int MAX_WBITS = 15; + + /// + /// Internal compression engine constant + /// + public const int WSIZE = 1 << MAX_WBITS; + + /// + /// Internal compression engine constant + /// + public const int WMASK = WSIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; + + /// + /// Internal compression engine constant + /// + public const int HASH_SIZE = 1 << HASH_BITS; + + /// + /// Internal compression engine constant + /// + public const int HASH_MASK = HASH_SIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; + + /// + /// Internal compression engine constant + /// + public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + + /// + /// Internal compression engine constant + /// + public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; + + /// + /// Internal compression engine constant + /// + public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); + + /// + /// Internal compression engine constant + /// + public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_STORED = 0; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_FAST = 1; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_SLOW = 2; + + /// + /// Internal compression engine constant + /// + public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; + + /// + /// Internal compression engine constant + /// + public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs new file mode 100644 index 0000000000..c1c86a98be --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs @@ -0,0 +1,858 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Strategies for deflater + /// + internal enum DeflateStrategy + { + /// + /// The default strategy + /// + Default = 0, + + /// + /// This strategy will only allow longer string repetitions. It is + /// useful for random data with a small character set. + /// + Filtered = 1, + + /// + /// This strategy will not look for string repetitions at all. It + /// only encodes with Huffman trees (which means, that more common + /// characters get a smaller encoding. + /// + HuffmanOnly = 2 + } + + // DEFLATE ALGORITHM: + // + // The uncompressed stream is inserted into the window array. When + // the window array is full the first half is thrown away and the + // second half is copied to the beginning. + // + // The head array is a hash table. Three characters build a hash value + // and they the value points to the corresponding index in window of + // the last string with this hash. The prev array implements a + // linked list of matches with the same hash: prev[index & WMASK] points + // to the previous index with the same hash. + // + + /// + /// Low level compression engine for deflate algorithm which uses a 32K sliding window + /// with secondary compression from Huffman/Shannon-Fano codes. + /// + internal sealed unsafe class DeflaterEngine : IDisposable + { + private const int TooFar = 4096; + + // Hash index of string to be inserted + private int insertHashIndex; + + private int matchStart; + + // Length of best match + private int matchLen; + + // Set if previous match exists + private bool prevAvailable; + + private int blockStart; + + /// + /// Points to the current character in the window. + /// + private int strstart; + + /// + /// lookahead is the number of characters starting at strstart in + /// window that are valid. + /// So window[strstart] until window[strstart+lookahead-1] are valid + /// characters. + /// + private int lookahead; + + /// + /// The current compression function. + /// + private int compressionFunction; + + /// + /// The input data for compression. + /// + private byte[] inputBuf; + + /// + /// The offset into inputBuf, where input data starts. + /// + private int inputOff; + + /// + /// The end offset of the input data. + /// + private int inputEnd; + + private readonly DeflateStrategy strategy; + private DeflaterHuffman huffman; + private bool isDisposed; + + /// + /// Hashtable, hashing three characters to an index for window, so + /// that window[index]..window[index+2] have this hash code. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xFFFF. + /// + private IMemoryOwner headMemoryOwner; + private MemoryHandle headMemoryHandle; + private readonly Memory head; + private readonly short* pinnedHeadPointer; + + /// + /// prev[index & WMASK] points to the previous index that has the + /// same hash code as the string starting at index. This way + /// entries with the same hash code are in a linked list. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xFFFF. + /// + private IMemoryOwner prevMemoryOwner; + private MemoryHandle prevMemoryHandle; + private readonly Memory prev; + private readonly short* pinnedPrevPointer; + + /// + /// This array contains the part of the uncompressed stream that + /// is of relevance. The current character is indexed by strstart. + /// + private IManagedByteBuffer windowMemoryOwner; + private MemoryHandle windowMemoryHandle; + private readonly byte[] window; + private readonly byte* pinnedWindowPointer; + + private int maxChain; + private int maxLazy; + private int niceLength; + private int goodLength; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The deflate strategy to use. + public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy) + { + this.huffman = new DeflaterHuffman(memoryAllocator); + this.Pending = this.huffman.Pending; + this.strategy = strategy; + + // Create pinned pointers to the various buffers to allow indexing + // without bounds checks. + this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); + this.window = this.windowMemoryOwner.Array; + this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin(); + this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; + + this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); + this.head = this.headMemoryOwner.Memory; + this.headMemoryHandle = this.headMemoryOwner.Memory.Pin(); + this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; + + this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); + this.prev = this.prevMemoryOwner.Memory; + this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin(); + this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; + + // We start at index 1, to avoid an implementation deficiency, that + // we cannot build a repeat pattern at index 0. + this.blockStart = this.strstart = 1; + } + + /// + /// Gets the pending buffer to use. + /// + public DeflaterPendingBuffer Pending { get; } + + /// + /// Deflate drives actual compression of data + /// + /// True to flush input buffers + /// Finish deflation with the current input. + /// Returns true if progress has been made. + public bool Deflate(bool flush, bool finish) + { + bool progress = false; + do + { + this.FillWindow(); + bool canFlush = flush && (this.inputOff == this.inputEnd); + + switch (this.compressionFunction) + { + case DeflaterConstants.DEFLATE_STORED: + progress = this.DeflateStored(canFlush, finish); + break; + + case DeflaterConstants.DEFLATE_FAST: + progress = this.DeflateFast(canFlush, finish); + break; + + case DeflaterConstants.DEFLATE_SLOW: + progress = this.DeflateSlow(canFlush, finish); + break; + + default: + DeflateThrowHelper.ThrowUnknownCompression(); + break; + } + } + while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made + return progress; + } + + /// + /// Sets input data to be deflated. Should only be called when + /// returns true + /// + /// The buffer containing input data. + /// The offset of the first byte of data. + /// The number of bytes of data to use as input. + public void SetInput(byte[] buffer, int offset, int count) + { + if (buffer is null) + { + DeflateThrowHelper.ThrowNull(nameof(buffer)); + } + + if (offset < 0) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(offset)); + } + + if (count < 0) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(count)); + } + + if (this.inputOff < this.inputEnd) + { + DeflateThrowHelper.ThrowNotProcessed(); + } + + int end = offset + count; + + // We want to throw an ArgumentOutOfRangeException early. + // The check is very tricky: it also handles integer wrap around. + if ((offset > end) || (end > buffer.Length)) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(count)); + } + + this.inputBuf = buffer; + this.inputOff = offset; + this.inputEnd = end; + } + + /// + /// Determines if more input is needed. + /// + /// Return true if input is needed via SetInput + [MethodImpl(InliningOptions.ShortMethod)] + public bool NeedsInput() => this.inputEnd == this.inputOff; + + /// + /// Reset internal state + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.huffman.Reset(); + this.blockStart = this.strstart = 1; + this.lookahead = 0; + this.prevAvailable = false; + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + this.head.Span.Slice(0, DeflaterConstants.HASH_SIZE).Clear(); + this.prev.Span.Slice(0, DeflaterConstants.WSIZE).Clear(); + } + + /// + /// Set the deflate level (0-9) + /// + /// The value to set the level to. + public void SetLevel(int level) + { + if ((level < 0) || (level > 9)) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(level)); + } + + this.goodLength = DeflaterConstants.GOOD_LENGTH[level]; + this.maxLazy = DeflaterConstants.MAX_LAZY[level]; + this.niceLength = DeflaterConstants.NICE_LENGTH[level]; + this.maxChain = DeflaterConstants.MAX_CHAIN[level]; + + if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction) + { + switch (this.compressionFunction) + { + case DeflaterConstants.DEFLATE_STORED: + if (this.strstart > this.blockStart) + { + this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.blockStart = this.strstart; + } + + this.UpdateHash(); + break; + + case DeflaterConstants.DEFLATE_FAST: + if (this.strstart > this.blockStart) + { + this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.blockStart = this.strstart; + } + + break; + + case DeflaterConstants.DEFLATE_SLOW: + if (this.prevAvailable) + { + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF); + } + + if (this.strstart > this.blockStart) + { + this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.blockStart = this.strstart; + } + + this.prevAvailable = false; + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + break; + } + + this.compressionFunction = DeflaterConstants.COMPR_FUNC[level]; + } + } + + /// + /// Fill the window + /// + public void FillWindow() + { + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) + { + this.SlideWindow(); + } + + // If there is not enough lookahead, but still some input left, read in the input. + if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd) + { + int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart; + + if (more > this.inputEnd - this.inputOff) + { + more = this.inputEnd - this.inputOff; + } + + Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more); + + this.inputOff += more; + this.lookahead += more; + } + + if (this.lookahead >= DeflaterConstants.MIN_MATCH) + { + this.UpdateHash(); + } + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.huffman.Dispose(); + + this.windowMemoryHandle.Dispose(); + this.windowMemoryOwner.Dispose(); + + this.headMemoryHandle.Dispose(); + this.headMemoryOwner.Dispose(); + + this.prevMemoryHandle.Dispose(); + this.prevMemoryOwner.Dispose(); + + this.windowMemoryOwner = null; + this.headMemoryOwner = null; + this.prevMemoryOwner = null; + this.huffman = null; + + this.isDisposed = true; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void UpdateHash() + { + byte* pinned = this.pinnedWindowPointer; + this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1]; + } + + /// + /// Inserts the current string in the head hash and returns the previous + /// value for this hash. + /// + /// The previous hash value + [MethodImpl(InliningOptions.ShortMethod)] + private int InsertString() + { + short match; + int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; + + short* pinnedHead = this.pinnedHeadPointer; + this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash]; + pinnedHead[hash] = unchecked((short)this.strstart); + this.insertHashIndex = hash; + return match & 0xFFFF; + } + + private void SlideWindow() + { + Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE); + this.matchStart -= DeflaterConstants.WSIZE; + this.strstart -= DeflaterConstants.WSIZE; + this.blockStart -= DeflaterConstants.WSIZE; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). + short* pinnedHead = this.pinnedHeadPointer; + for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) + { + int m = pinnedHead[i] & 0xFFFF; + pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); + } + + // Slide the prev table. + short* pinnedPrev = this.pinnedPrevPointer; + for (int i = 0; i < DeflaterConstants.WSIZE; i++) + { + int m = pinnedPrev[i] & 0xFFFF; + pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); + } + } + + /// + /// + /// Find the best (longest) string in the window matching the + /// string starting at strstart. + /// + /// + /// Preconditions: + /// + /// strstart + DeflaterConstants.MAX_MATCH <= window.length. + /// + /// + /// The current match. + /// True if a match greater than the minimum length is found + [MethodImpl(InliningOptions.HotPath)] + private bool FindLongestMatch(int curMatch) + { + int match; + int scan = this.strstart; + + // scanMax is the highest position that we can look at + int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1; + int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); + + int chainLength = this.maxChain; + int niceLength = Math.Min(this.niceLength, this.lookahead); + + int matchStrt = this.matchStart; + this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1); + int matchLength = this.matchLen; + + if (scan + matchLength > scanMax) + { + return false; + } + + byte* pinnedWindow = this.pinnedWindowPointer; + int scanStart = this.strstart; + byte scanEnd1 = pinnedWindow[scan + matchLength - 1]; + byte scanEnd = pinnedWindow[scan + matchLength]; + + // Do not waste too much time if we already have a good match: + if (matchLength >= this.goodLength) + { + chainLength >>= 2; + } + + short* pinnedPrev = this.pinnedPrevPointer; + do + { + match = curMatch; + scan = scanStart; + + if (pinnedWindow[match + matchLength] != scanEnd + || pinnedWindow[match + matchLength - 1] != scanEnd1 + || pinnedWindow[match] != pinnedWindow[scan] + || pinnedWindow[++match] != pinnedWindow[++scan]) + { + continue; + } + + // scan is set to strstart+1 and the comparison passed, so + // scanMax - scan is the maximum number of bytes we can compare. + // below we compare 8 bytes at a time, so first we compare + // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 + // n & (8 - 1) == n % 8. + switch ((scanMax - scan) & 7) + { + case 1: + if (pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + + case 2: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + + case 3: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + + case 4: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + + case 5: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + + case 6: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + + case 7: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { + break; + } + + break; + } + + if (pinnedWindow[scan] == pinnedWindow[match]) + { + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart + 258 unless lookahead is + // exhausted first. + do + { + if (scan == scanMax) + { + ++scan; // advance to first position not matched + ++match; + + break; + } + } + while (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]); + } + + if (scan - scanStart > matchLength) + { + matchStrt = curMatch; + matchLength = scan - scanStart; + + if (matchLength >= niceLength) + { + break; + } + + scanEnd1 = pinnedWindow[scan - 1]; + scanEnd = pinnedWindow[scan]; + } + } + while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0); + + this.matchStart = matchStrt; + this.matchLen = matchLength; + return matchLength >= DeflaterConstants.MIN_MATCH; + } + + private bool DeflateStored(bool flush, bool finish) + { + if (!flush && (this.lookahead == 0)) + { + return false; + } + + this.strstart += this.lookahead; + this.lookahead = 0; + + int storedLength = this.strstart - this.blockStart; + + if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full + (this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window + flush) + { + bool lastBlock = finish; + if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) + { + storedLength = DeflaterConstants.MAX_BLOCK_SIZE; + lastBlock = false; + } + + this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock); + this.blockStart += storedLength; + return !(lastBlock || storedLength == 0); + } + + return true; + } + + private bool DeflateFast(bool flush, bool finish) + { + if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + { + return false; + } + + while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (this.lookahead == 0) + { + // We are flushing everything + this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.blockStart = this.strstart; + return false; + } + + if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + { + // slide window, as FindLongestMatch needs this. + // This should only happen when flushing and the window + // is almost full. + this.SlideWindow(); + } + + int hashHead; + if (this.lookahead >= DeflaterConstants.MIN_MATCH && + (hashHead = this.InsertString()) != 0 && + this.strategy != DeflateStrategy.HuffmanOnly && + this.strstart - hashHead <= DeflaterConstants.MAX_DIST && + this.FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen + bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen); + + this.lookahead -= this.matchLen; + if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH) + { + while (--this.matchLen > 0) + { + ++this.strstart; + this.InsertString(); + } + + ++this.strstart; + } + else + { + this.strstart += this.matchLen; + if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1) + { + this.UpdateHash(); + } + } + + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + if (!full) + { + continue; + } + } + else + { + // No match found + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff); + ++this.strstart; + --this.lookahead; + } + + if (this.huffman.IsFull()) + { + bool lastBlock = finish && (this.lookahead == 0); + this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock); + this.blockStart = this.strstart; + return !lastBlock; + } + } + + return true; + } + + private bool DeflateSlow(bool flush, bool finish) + { + if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + { + return false; + } + + while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (this.lookahead == 0) + { + if (this.prevAvailable) + { + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); + } + + this.prevAvailable = false; + + // We are flushing everything + this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.blockStart = this.strstart; + return false; + } + + if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + { + // slide window, as FindLongestMatch needs this. + // This should only happen when flushing and the window + // is almost full. + this.SlideWindow(); + } + + int prevMatch = this.matchStart; + int prevLen = this.matchLen; + if (this.lookahead >= DeflaterConstants.MIN_MATCH) + { + int hashHead = this.InsertString(); + + if (this.strategy != DeflateStrategy.HuffmanOnly && + hashHead != 0 && + this.strstart - hashHead <= DeflaterConstants.MAX_DIST && + this.FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen + // Discard match if too small and too far away + if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar))) + { + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + } + } + } + + // previous match was better + if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen)) + { + this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen); + prevLen -= 2; + do + { + this.strstart++; + this.lookahead--; + if (this.lookahead >= DeflaterConstants.MIN_MATCH) + { + this.InsertString(); + } + } + while (--prevLen > 0); + + this.strstart++; + this.lookahead--; + this.prevAvailable = false; + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + } + else + { + if (this.prevAvailable) + { + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); + } + + this.prevAvailable = true; + this.strstart++; + this.lookahead--; + } + + if (this.huffman.IsFull()) + { + int len = this.strstart - this.blockStart; + if (this.prevAvailable) + { + len--; + } + + bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; + this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock); + this.blockStart += len; + return !lastBlock; + } + } + + return true; + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs new file mode 100644 index 0000000000..022d3d4d0d --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -0,0 +1,989 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Performs Deflate Huffman encoding. + /// + internal sealed unsafe class DeflaterHuffman : IDisposable + { + private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); + + // The number of literal codes. + private const int LiteralNumber = 286; + + // Number of distance codes + private const int DistanceNumber = 30; + + // Number of codes used to transfer bit lengths + private const int BitLengthNumber = 19; + + // Repeat previous bit length 3-6 times (2 bits of repeat count) + private const int Repeat3To6 = 16; + + // Repeat a zero length 3-10 times (3 bits of repeat count) + private const int Repeat3To10 = 17; + + // Repeat a zero length 11-138 times (7 bits of repeat count) + private const int Repeat11To138 = 18; + + private const int EofSymbol = 256; + + private Tree literalTree; + private Tree distTree; + private Tree blTree; + + // Buffer for distances + private readonly IMemoryOwner distanceManagedBuffer; + private readonly short* pinnedDistanceBuffer; + private MemoryHandle distanceBufferHandle; + + private readonly IMemoryOwner literalManagedBuffer; + private readonly short* pinnedLiteralBuffer; + private MemoryHandle literalBufferHandle; + + private int lastLiteral; + private int extraBits; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + public DeflaterHuffman(MemoryAllocator memoryAllocator) + { + this.Pending = new DeflaterPendingBuffer(memoryAllocator); + + this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15); + this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); + this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); + + this.distanceManagedBuffer = memoryAllocator.Allocate(BufferSize); + this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin(); + this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; + + this.literalManagedBuffer = memoryAllocator.Allocate(BufferSize); + this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin(); + this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; + } + +#pragma warning disable SA1201 // Elements should appear in the correct order + + // See RFC 1951 3.2.6 + // Literal codes + private static readonly short[] StaticLCodes = new short[] + { + 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, + 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, + 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, + 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19, + 275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499, + 11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491, + 27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507, + 7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487, + 23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503, + 15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495, + 31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511, + 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, + 100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163 + }; + + private static ReadOnlySpan StaticLLength => new byte[] + { + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8 + }; + + // Distance codes and lengths. + private static readonly short[] StaticDCodes = new short[] + { + 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, + 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23 + }; + + private static ReadOnlySpan StaticDLength => new byte[] + { + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + }; +#pragma warning restore SA1201 // Elements should appear in the correct order + + /// + /// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes. + /// + private static ReadOnlySpan BitLengthOrder => new byte[] + { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + private static ReadOnlySpan Bit4Reverse => new byte[] + { + 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 + }; + + /// + /// Gets the pending buffer to use. + /// + public DeflaterPendingBuffer Pending { get; private set; } + + /// + /// Reset internal state + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.lastLiteral = 0; + this.extraBits = 0; + this.literalTree.Reset(); + this.distTree.Reset(); + this.blTree.Reset(); + } + + /// + /// Write all trees to pending buffer + /// + /// The number/rank of treecodes to send. + public void SendAllTrees(int blTreeCodes) + { + this.blTree.BuildCodes(); + this.literalTree.BuildCodes(); + this.distTree.BuildCodes(); + this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5); + this.Pending.WriteBits(this.distTree.NumCodes - 1, 5); + this.Pending.WriteBits(blTreeCodes - 4, 4); + + for (int rank = 0; rank < blTreeCodes; rank++) + { + this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); + } + + this.literalTree.WriteTree(this.Pending, this.blTree); + this.distTree.WriteTree(this.Pending, this.blTree); + } + + /// + /// Compress current buffer writing data to pending buffer + /// + public void CompressBlock() + { + DeflaterPendingBuffer pendingBuffer = this.Pending; + short* pinnedDistance = this.pinnedDistanceBuffer; + short* pinnedLiteral = this.pinnedLiteralBuffer; + + for (int i = 0; i < this.lastLiteral; i++) + { + int litlen = pinnedLiteral[i] & 0xFF; + int dist = pinnedDistance[i]; + if (dist-- != 0) + { + int lc = Lcode(litlen); + this.literalTree.WriteSymbol(pendingBuffer, lc); + + int bits = (lc - 261) / 4; + if (bits > 0 && bits <= 5) + { + this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits); + } + + int dc = Dcode(dist); + this.distTree.WriteSymbol(pendingBuffer, dc); + + bits = (dc >> 1) - 1; + if (bits > 0) + { + this.Pending.WriteBits(dist & ((1 << bits) - 1), bits); + } + } + else + { + this.literalTree.WriteSymbol(pendingBuffer, litlen); + } + } + + this.literalTree.WriteSymbol(pendingBuffer, EofSymbol); + } + + /// + /// Flush block to output with no compression + /// + /// Data to write + /// Index of first byte to write + /// Count of bytes to write + /// True if this is the last block + [MethodImpl(InliningOptions.ShortMethod)] + public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { + this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); + this.Pending.AlignToByte(); + this.Pending.WriteShort(storedLength); + this.Pending.WriteShort(~storedLength); + this.Pending.WriteBlock(stored, storedOffset, storedLength); + this.Reset(); + } + + /// + /// Flush block to output with compression + /// + /// Data to flush + /// Index of first byte to flush + /// Count of bytes to flush + /// True if this is the last block + public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { + this.literalTree.Frequencies[EofSymbol]++; + + // Build trees + this.literalTree.BuildTree(); + this.distTree.BuildTree(); + + // Calculate bitlen frequency + this.literalTree.CalcBLFreq(this.blTree); + this.distTree.CalcBLFreq(this.blTree); + + // Build bitlen tree + this.blTree.BuildTree(); + + int blTreeCodes = 4; + + for (int i = 18; i > blTreeCodes; i--) + { + if (this.blTree.Length[BitLengthOrder[i]] > 0) + { + blTreeCodes = i + 1; + } + } + + int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength() + + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength() + + this.extraBits; + + int static_len = this.extraBits; + ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); + for (int i = 0; i < LiteralNumber; i++) + { + static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); + } + + ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); + for (int i = 0; i < DistanceNumber; i++) + { + static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); + } + + if (opt_len >= static_len) + { + // Force static trees + opt_len = static_len; + } + + if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) + { + // Store Block + this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + } + else if (opt_len == static_len) + { + // Encode with static tree + this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); + this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength); + this.distTree.SetStaticCodes(StaticDCodes, StaticDLength); + this.CompressBlock(); + this.Reset(); + } + else + { + // Encode with dynamic tree + this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); + this.SendAllTrees(blTreeCodes); + this.CompressBlock(); + this.Reset(); + } + } + + /// + /// Get value indicating if internal buffer is full + /// + /// true if buffer is full + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsFull() => this.lastLiteral >= BufferSize; + + /// + /// Add literal to buffer + /// + /// Literal value to add to buffer. + /// Value indicating internal buffer is full + [MethodImpl(InliningOptions.ShortMethod)] + public bool TallyLit(int literal) + { + this.pinnedDistanceBuffer[this.lastLiteral] = 0; + this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal; + this.literalTree.Frequencies[literal]++; + return this.IsFull(); + } + + /// + /// Add distance code and length to literal and distance trees + /// + /// Distance code + /// Length + /// Value indicating if internal buffer is full + [MethodImpl(InliningOptions.ShortMethod)] + public bool TallyDist(int distance, int length) + { + this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance; + this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3); + + int lc = Lcode(length - 3); + this.literalTree.Frequencies[lc]++; + if (lc >= 265 && lc < 285) + { + this.extraBits += (lc - 261) / 4; + } + + int dc = Dcode(distance - 1); + this.distTree.Frequencies[dc]++; + if (dc >= 4) + { + this.extraBits += (dc >> 1) - 1; + } + + return this.IsFull(); + } + + /// + /// Reverse the bits of a 16 bit value. + /// + /// Value to reverse bits + /// Value with bits reversed + [MethodImpl(InliningOptions.ShortMethod)] + public static short BitReverse(int toReverse) + { + /* Use unsafe offsetting and manually validate the input index to reduce the + * total number of conditional branches. There are two main cases to test here: + * 1. In the first 3, the input value (or some combination of it) is combined + * with & 0xF, which results in a maximum value of 0xF no matter what the + * input value was. That is 15, which is always in range for the target span. + * As a result, no input validation is needed at all in this case. + * 2. There are two cases where the input value might cause an invalid access: + * when it is either negative, or greater than 15 << 12. We can test both + * conditions in a single pass by casting the input value to uint and right + * shifting it by 12, which also preserves the sign. If it is a negative + * value (2-complement), the test will fail as the uint cast will result + * in a much larger value. If the value was simply too high, the test will + * fail as expected. We can't simply check whether the value is lower than + * 15 << 12, because higher values are acceptable in the first 3 accesses. + * Doing this reduces the total number of index checks from 4 down to just 1. */ + int toReverseRightShiftBy12 = toReverse >> 12; + Guard.MustBeLessThanOrEqualTo((uint)toReverseRightShiftBy12, 15, nameof(toReverse)); + + ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); + + return (short)(Unsafe.Add(ref bit4ReverseRef, toReverse & 0xF) << 12 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 4) & 0xF) << 8 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 8) & 0xF) << 4 + | Unsafe.Add(ref bit4ReverseRef, toReverseRightShiftBy12)); + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.Pending.Dispose(); + this.distanceBufferHandle.Dispose(); + this.distanceManagedBuffer.Dispose(); + this.literalBufferHandle.Dispose(); + this.literalManagedBuffer.Dispose(); + + this.literalTree.Dispose(); + this.blTree.Dispose(); + this.distTree.Dispose(); + + this.Pending = null; + this.literalTree = null; + this.blTree = null; + this.distTree = null; + this.isDisposed = true; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Lcode(int length) + { + if (length == 255) + { + return 285; + } + + int code = 257; + while (length >= 8) + { + code += 4; + length >>= 1; + } + + return code + length; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Dcode(int distance) + { + int code = 0; + while (distance >= 4) + { + code += 2; + distance >>= 1; + } + + return code + distance; + } + + private sealed class Tree : IDisposable + { + private readonly int minNumCodes; + private readonly int[] bitLengthCounts; + private readonly int maxLength; + private bool isDisposed; + + private readonly int elementCount; + + private readonly MemoryAllocator memoryAllocator; + + private IMemoryOwner codesMemoryOwner; + private MemoryHandle codesMemoryHandle; + private readonly short* codes; + + private IMemoryOwner frequenciesMemoryOwner; + private MemoryHandle frequenciesMemoryHandle; + + private IManagedByteBuffer lengthsMemoryOwner; + private MemoryHandle lengthsMemoryHandle; + + public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) + { + this.memoryAllocator = memoryAllocator; + this.elementCount = elements; + this.minNumCodes = minCodes; + this.maxLength = maxLength; + + this.frequenciesMemoryOwner = memoryAllocator.Allocate(elements); + this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); + this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; + + this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements); + this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); + this.Length = (byte*)this.lengthsMemoryHandle.Pointer; + + this.codesMemoryOwner = memoryAllocator.Allocate(elements); + this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin(); + this.codes = (short*)this.codesMemoryHandle.Pointer; + + // Maxes out at 15. + this.bitLengthCounts = new int[maxLength]; + } + + public int NumCodes { get; private set; } + + public short* Frequencies { get; } + + public byte* Length { get; } + + /// + /// Resets the internal state of the tree + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.frequenciesMemoryOwner.Memory.Span.Clear(); + this.lengthsMemoryOwner.Memory.Span.Clear(); + this.codesMemoryOwner.Memory.Span.Clear(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code) + => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]); + + /// + /// Set static codes and length + /// + /// new codes + /// length for new codes + [MethodImpl(InliningOptions.ShortMethod)] + public void SetStaticCodes(ReadOnlySpan staticCodes, ReadOnlySpan staticLengths) + { + staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span); + staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span); + } + + /// + /// Build dynamic codes and lengths + /// + public void BuildCodes() + { + // Maxes out at 15 * 4 + Span nextCode = stackalloc int[this.maxLength]; + ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode); + ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); + + int code = 0; + for (int bits = 0; bits < this.maxLength; bits++) + { + Unsafe.Add(ref nextCodeRef, bits) = code; + code += Unsafe.Add(ref bitLengthCountsRef, bits) << (15 - bits); + } + + for (int i = 0; i < this.NumCodes; i++) + { + int bits = this.Length[i]; + if (bits > 0) + { + this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, bits - 1)); + Unsafe.Add(ref nextCodeRef, bits - 1) += 1 << (16 - bits); + } + } + } + + [MethodImpl(InliningOptions.HotPath)] + public void BuildTree() + { + int numSymbols = this.elementCount; + + // heap is a priority queue, sorted by frequency, least frequent + // nodes first. The heap is a binary tree, with the property, that + // the parent node is smaller than both child nodes. This assures + // that the smallest node is the first parent. + // + // The binary tree is encoded in an array: 0 is root node and + // the nodes 2*n+1, 2*n+2 are the child nodes of node n. + // Maxes out at 286 * 4 so too large for the stack. + using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols)) + { + ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span); + + int heapLen = 0; + int maxCode = 0; + for (int n = 0; n < numSymbols; n++) + { + int freq = this.Frequencies[n]; + if (freq != 0) + { + // Insert n into heap + int pos = heapLen++; + int ppos; + while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) >> 1)] > freq) + { + Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos); + pos = ppos; + } + + Unsafe.Add(ref heapRef, pos) = n; + + maxCode = n; + } + } + + // We could encode a single literal with 0 bits but then we + // don't see the literals. Therefore we force at least two + // literals to avoid this case. We don't care about order in + // this case, both literals get a 1 bit code. + while (heapLen < 2) + { + Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0; + } + + this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); + + int numLeafs = heapLen; + int childrenLength = (4 * heapLen) - 2; + using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength)) + using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1)) + { + ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span); + ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span); + int numNodes = numLeafs; + + for (int i = 0; i < heapLen; i++) + { + int node = Unsafe.Add(ref heapRef, i); + int i2 = 2 * i; + Unsafe.Add(ref childrenRef, i2) = node; + Unsafe.Add(ref childrenRef, i2 + 1) = -1; + Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8; + Unsafe.Add(ref heapRef, i) = i; + } + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + do + { + int first = Unsafe.Add(ref heapRef, 0); + int last = Unsafe.Add(ref heapRef, --heapLen); + + // Propagate the hole to the leafs of the heap + int ppos = 0; + int path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) + { + path++; + } + + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (path * 2) + 1; + } + + // Now propagate the last element down along path. Normally + // it shouldn't go too deep. + int lastVal = Unsafe.Add(ref valuesRef, last); + while ((path = ppos) > 0 + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } + + Unsafe.Add(ref heapRef, path) = last; + + int second = Unsafe.Add(ref heapRef, 0); + + // Create a new node father of first and second + last = numNodes++; + Unsafe.Add(ref childrenRef, 2 * last) = first; + Unsafe.Add(ref childrenRef, (2 * last) + 1) = second; + int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF); + Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, second) - mindepth + 1; + + // Again, propagate the hole to the leafs + ppos = 0; + path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) + { + path++; + } + + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (ppos * 2) + 1; + } + + // Now propagate the new element down along path + while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } + + Unsafe.Add(ref heapRef, path) = last; + } + while (heapLen > 1); + + if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1) + { + DeflateThrowHelper.ThrowHeapViolated(); + } + + this.BuildLength(childrenMemoryOwner.Memory.Span); + } + } + } + + /// + /// Get encoded length + /// + /// Encoded length, the sum of frequencies * lengths + [MethodImpl(InliningOptions.ShortMethod)] + public int GetEncodedLength() + { + int len = 0; + for (int i = 0; i < this.elementCount; i++) + { + len += this.Frequencies[i] * this.Length[i]; + } + + return len; + } + + /// + /// Scan a literal or distance tree to determine the frequencies of the codes + /// in the bit length tree. + /// + public void CalcBLFreq(Tree blTree) + { + int maxCount; // max repeat count + int minCount; // min repeat count + int count; // repeat count of the current code + int curLen = -1; // length of current code + + int i = 0; + while (i < this.NumCodes) + { + count = 1; + int nextlen = this.Length[i]; + if (nextlen == 0) + { + maxCount = 138; + minCount = 3; + } + else + { + maxCount = 6; + minCount = 3; + if (curLen != nextlen) + { + blTree.Frequencies[nextlen]++; + count = 0; + } + } + + curLen = nextlen; + i++; + + while (i < this.NumCodes && curLen == this.Length[i]) + { + i++; + if (++count >= maxCount) + { + break; + } + } + + if (count < minCount) + { + blTree.Frequencies[curLen] += (short)count; + } + else if (curLen != 0) + { + blTree.Frequencies[Repeat3To6]++; + } + else if (count <= 10) + { + blTree.Frequencies[Repeat3To10]++; + } + else + { + blTree.Frequencies[Repeat11To138]++; + } + } + } + + /// + /// Write the tree values. + /// + /// The pending buffer. + /// The tree to write. + public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree) + { + int maxCount; // max repeat count + int minCount; // min repeat count + int count; // repeat count of the current code + int curLen = -1; // length of current code + + int i = 0; + while (i < this.NumCodes) + { + count = 1; + int nextlen = this.Length[i]; + if (nextlen == 0) + { + maxCount = 138; + minCount = 3; + } + else + { + maxCount = 6; + minCount = 3; + if (curLen != nextlen) + { + bitLengthTree.WriteSymbol(pendingBuffer, nextlen); + count = 0; + } + } + + curLen = nextlen; + i++; + + while (i < this.NumCodes && curLen == this.Length[i]) + { + i++; + if (++count >= maxCount) + { + break; + } + } + + if (count < minCount) + { + while (count-- > 0) + { + bitLengthTree.WriteSymbol(pendingBuffer, curLen); + } + } + else if (curLen != 0) + { + bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6); + pendingBuffer.WriteBits(count - 3, 2); + } + else if (count <= 10) + { + bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10); + pendingBuffer.WriteBits(count - 3, 3); + } + else + { + bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138); + pendingBuffer.WriteBits(count - 11, 7); + } + } + } + + private void BuildLength(ReadOnlySpan children) + { + byte* lengthPtr = this.Length; + ref int childrenRef = ref MemoryMarshal.GetReference(children); + ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); + + int maxLen = this.maxLength; + int numNodes = children.Length >> 1; + int numLeafs = (numNodes + 1) >> 1; + int overflow = 0; + + Array.Clear(this.bitLengthCounts, 0, maxLen); + + // First calculate optimal bit lengths + using (IMemoryOwner lengthsMemoryOwner = this.memoryAllocator.Allocate(numNodes, AllocationOptions.Clean)) + { + ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span); + + for (int i = numNodes - 1; i >= 0; i--) + { + if (children[(2 * i) + 1] != -1) + { + int bitLength = Unsafe.Add(ref lengthsRef, i) + 1; + if (bitLength > maxLen) + { + bitLength = maxLen; + overflow++; + } + + Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, 2 * i)) = Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, (2 * i) + 1)) = bitLength; + } + else + { + // A leaf node + int bitLength = Unsafe.Add(ref lengthsRef, i); + Unsafe.Add(ref bitLengthCountsRef, bitLength - 1)++; + lengthPtr[Unsafe.Add(ref childrenRef, 2 * i)] = (byte)Unsafe.Add(ref lengthsRef, i); + } + } + } + + if (overflow == 0) + { + return; + } + + int incrBitLen = maxLen - 1; + do + { + // Find the first bit length which could increase: + while (Unsafe.Add(ref bitLengthCountsRef, --incrBitLen) == 0) + { + } + + // Move this node one down and remove a corresponding + // number of overflow nodes. + do + { + Unsafe.Add(ref bitLengthCountsRef, incrBitLen)--; + Unsafe.Add(ref bitLengthCountsRef, ++incrBitLen)++; + overflow -= 1 << (maxLen - 1 - incrBitLen); + } + while (overflow > 0 && incrBitLen < maxLen - 1); + } + while (overflow > 0); + + // We may have overshot above. Move some nodes from maxLength to + // maxLength-1 in that case. + Unsafe.Add(ref bitLengthCountsRef, maxLen - 1) += overflow; + Unsafe.Add(ref bitLengthCountsRef, maxLen - 2) -= overflow; + + // Now recompute all bit lengths, scanning in increasing + // frequency. It is simpler to reconstruct all lengths instead of + // fixing only the wrong ones. This idea is taken from 'ar' + // written by Haruhiko Okumura. + // + // The nodes were inserted with decreasing frequency into the childs + // array. + int nodeIndex = 2 * numLeafs; + for (int bits = maxLen; bits != 0; bits--) + { + int n = Unsafe.Add(ref bitLengthCountsRef, bits - 1); + while (n > 0) + { + int childIndex = 2 * Unsafe.Add(ref childrenRef, nodeIndex++); + if (Unsafe.Add(ref childrenRef, childIndex + 1) == -1) + { + // We found another leaf + lengthPtr[Unsafe.Add(ref childrenRef, childIndex)] = (byte)bits; + n--; + } + } + } + } + + public void Dispose() + { + if (!this.isDisposed) + { + this.frequenciesMemoryHandle.Dispose(); + this.frequenciesMemoryOwner.Dispose(); + + this.lengthsMemoryHandle.Dispose(); + this.lengthsMemoryOwner.Dispose(); + + this.codesMemoryHandle.Dispose(); + this.codesMemoryOwner.Dispose(); + + this.frequenciesMemoryOwner = null; + this.lengthsMemoryOwner = null; + this.codesMemoryOwner = null; + + this.isDisposed = true; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs new file mode 100644 index 0000000000..a777e6f7db --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs @@ -0,0 +1,153 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// A special stream deflating or compressing the bytes that are + /// written to it. It uses a Deflater to perform actual deflating. + /// + internal sealed class DeflaterOutputStream : Stream + { + private const int BufferLength = 512; + private IManagedByteBuffer memoryOwner; + private readonly byte[] buffer; + private Deflater deflater; + private readonly Stream rawStream; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The output stream where deflated output is written. + /// The compression level. + public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) + { + this.rawStream = rawStream; + this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength); + this.buffer = this.memoryOwner.Array; + this.deflater = new Deflater(memoryAllocator, compressionLevel); + } + + /// + public override bool CanRead => false; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => this.rawStream.CanWrite; + + /// + public override long Length => this.rawStream.Length; + + /// + public override long Position + { + get + { + return this.rawStream.Position; + } + + set + { + throw new NotSupportedException(); + } + } + + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + public override int ReadByte() => throw new NotSupportedException(); + + /// + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + /// + public override void Flush() + { + this.deflater.Flush(); + this.Deflate(true); + this.rawStream.Flush(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.deflater.SetInput(buffer, offset, count); + this.Deflate(); + } + + private void Deflate() => this.Deflate(false); + + private void Deflate(bool flushing) + { + while (flushing || !this.deflater.IsNeedingInput) + { + int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength); + + if (deflateCount <= 0) + { + break; + } + + this.rawStream.Write(this.buffer, 0, deflateCount); + } + + if (!this.deflater.IsNeedingInput) + { + DeflateThrowHelper.ThrowNoDeflate(); + } + } + + private void Finish() + { + this.deflater.Finish(); + while (!this.deflater.IsFinished) + { + int len = this.deflater.Deflate(this.buffer, 0, BufferLength); + if (len <= 0) + { + break; + } + + this.rawStream.Write(this.buffer, 0, len); + } + + if (!this.deflater.IsFinished) + { + DeflateThrowHelper.ThrowNoDeflate(); + } + + this.rawStream.Flush(); + } + + /// + protected override void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.Finish(); + this.deflater.Dispose(); + this.memoryOwner.Dispose(); + } + + this.deflater = null; + this.memoryOwner = null; + this.isDisposed = true; + base.Dispose(disposing); + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs new file mode 100644 index 0000000000..0414ca2f87 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Stores pending data for writing data to the Deflater. + /// + internal sealed unsafe class DeflaterPendingBuffer : IDisposable + { + private readonly byte[] buffer; + private readonly byte* pinnedBuffer; + private IManagedByteBuffer bufferMemoryOwner; + private MemoryHandle bufferMemoryHandle; + + private int start; + private int end; + private uint bits; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) + { + this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE); + this.buffer = this.bufferMemoryOwner.Array; + this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin(); + this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; + } + + /// + /// Gets the number of bits written to the buffer. + /// + public int BitCount { get; private set; } + + /// + /// Gets a value indicating whether indicates the buffer has been flushed. + /// + public bool IsFlushed => this.end == 0; + + /// + /// Clear internal state/buffers. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() => this.start = this.end = this.BitCount = 0; + + /// + /// Write a short value to buffer LSB first. + /// + /// The value to write. + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteShort(int value) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)value); + pinned[this.end++] = unchecked((byte)(value >> 8)); + } + + /// + /// Write a block of data to the internal buffer. + /// + /// The data to write. + /// The offset of first byte to write. + /// The number of bytes to write. + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteBlock(byte[] block, int offset, int length) + { + Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length)); + this.end += length; + } + + /// + /// Aligns internal buffer on a byte boundary. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void AlignToByte() + { + if (this.BitCount > 0) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)this.bits); + if (this.BitCount > 8) + { + pinned[this.end++] = unchecked((byte)(this.bits >> 8)); + } + } + + this.bits = 0; + this.BitCount = 0; + } + + /// + /// Write bits to internal buffer + /// + /// source of bits + /// number of bits to write + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteBits(int b, int count) + { + this.bits |= (uint)(b << this.BitCount); + this.BitCount += count; + if (this.BitCount >= 16) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)this.bits); + pinned[this.end++] = unchecked((byte)(this.bits >> 8)); + this.bits >>= 16; + this.BitCount -= 16; + } + } + + /// + /// Write a short value to internal buffer most significant byte first + /// + /// The value to write + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteShortMSB(int value) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)(value >> 8)); + pinned[this.end++] = unchecked((byte)value); + } + + /// + /// Flushes the pending buffer into the given output array. + /// If the output array is to small, only a partial flush is done. + /// + /// The output array. + /// The offset into output array. + /// The maximum number of bytes to store. + /// The number of bytes flushed. + public int Flush(byte[] output, int offset, int length) + { + if (this.BitCount >= 8) + { + this.pinnedBuffer[this.end++] = unchecked((byte)this.bits); + this.bits >>= 8; + this.BitCount -= 8; + } + + if (length > this.end - this.start) + { + length = this.end - this.start; + + Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + this.start = 0; + this.end = 0; + } + else + { + Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + this.start += length; + } + + return length; + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.bufferMemoryHandle.Dispose(); + this.bufferMemoryOwner.Dispose(); + this.bufferMemoryOwner = null; + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Formats/Png/Zlib/README.md index c297a91d5e..59f75d05f6 100644 --- a/src/ImageSharp/Formats/Png/Zlib/README.md +++ b/src/ImageSharp/Formats/Png/Zlib/README.md @@ -1,2 +1,5 @@ -Adler32.cs and Crc32.cs have been copied from -https://github.com/ygrenier/SharpZipLib.Portable +Deflatestream implementation adapted from + +https://github.com/icsharpcode/SharpZipLib + +LIcensed under MIT diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index 8e0bac938f..c723b463f8 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; -using System.IO.Compression; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -38,14 +38,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// The stream responsible for compressing the input stream. /// - private System.IO.Compression.DeflateStream deflateStream; + // private DeflateStream deflateStream; + private DeflaterOutputStream deflateStream; /// /// Initializes a new instance of the class. /// + /// The memory allocator to use for buffer allocations. /// The stream to compress. /// The compression level. - public ZlibDeflateStream(Stream stream, int compressionLevel) + public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, int compressionLevel) { this.rawStream = stream; @@ -60,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // +---+---+ // |CMF|FLG| // +---+---+ - int cmf = 0x78; + const int Cmf = 0x78; int flg = 218; // http://stackoverflow.com/a/2331025/277304 @@ -78,29 +80,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } // Just in case - flg -= ((cmf * 256) + flg) % 31; + flg -= ((Cmf * 256) + flg) % 31; if (flg < 0) { flg += 31; } - this.rawStream.WriteByte((byte)cmf); + this.rawStream.WriteByte(Cmf); this.rawStream.WriteByte((byte)flg); - // Initialize the deflate Stream. - CompressionLevel level = CompressionLevel.Optimal; - - if (compressionLevel >= 1 && compressionLevel <= 5) - { - level = CompressionLevel.Fastest; - } - else if (compressionLevel == 0) - { - level = CompressionLevel.NoCompression; - } - - this.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true); + this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel); } /// @@ -110,41 +100,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public override bool CanSeek => false; /// - public override bool CanWrite => true; + public override bool CanWrite => this.rawStream.CanWrite; /// - public override long Length => throw new NotSupportedException(); + public override long Length => this.rawStream.Length; /// public override long Position { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); + get + { + return this.rawStream.Position; + } + + set + { + throw new NotSupportedException(); + } } /// - public override void Flush() - { - this.deflateStream?.Flush(); - } + public override void Flush() => this.deflateStream.Flush(); /// - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } + public override void SetLength(long value) => throw new NotSupportedException(); /// public override void Write(byte[] buffer, int offset, int count) @@ -164,17 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (disposing) { // dispose managed resources - if (this.deflateStream != null) - { - this.deflateStream.Dispose(); - this.deflateStream = null; - } - else - { - // Hack: empty input? - this.rawStream.WriteByte(3); - this.rawStream.WriteByte(0); - } + this.deflateStream.Dispose(); // Add the crc uint crc = (uint)this.adler32.Value; @@ -184,11 +159,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.rawStream.WriteByte((byte)(crc & 0xFF)); } - base.Dispose(disposing); + this.deflateStream = null; - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. + base.Dispose(disposing); this.isDisposed = true; } } diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index e4645c44ac..3eb34b8617 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib internal sealed class ZlibInflateStream : Stream { /// - /// Used to read the Adler-32 and Crc-32 checksums + /// Used to read the Adler-32 and Crc-32 checksums. /// We don't actually use this for anything so it doesn't /// have to be threadsafe. /// @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private static readonly Func GetDataNoOp = () => 0; /// - /// The inner raw memory stream + /// The inner raw memory stream. /// private readonly Stream innerStream; @@ -43,12 +43,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private bool isDisposed; /// - /// The current data remaining to be read + /// The current data remaining to be read. /// private int currentDataRemaining; /// - /// Delegate to get more data once we've exhausted the current data remaining + /// Delegate to get more data once we've exhausted the current data remaining. /// private readonly Func getData; @@ -88,14 +88,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } /// - /// Gets the compressed stream over the deframed inner stream + /// Gets the compressed stream over the deframed inner stream. /// public DeflateStream CompressedStream { get; private set; } /// - /// Adds new bytes from a frame found in the original stream + /// Adds new bytes from a frame found in the original stream. /// - /// blabla + /// The current remaining data according to the chunk length. /// Whether the chunk to be inflated is a critical chunk. /// The . public bool AllocateNewBytes(int bytes, bool isCriticalChunk) @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { if (this.currentDataRemaining == 0) { - // last buffer was read in its entirety, let's make sure we don't actually have more + // Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks. this.currentDataRemaining = this.getData(); if (this.currentDataRemaining == 0) @@ -135,32 +135,35 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib int bytesToRead = Math.Min(count, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; - int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - long length = this.innerStream.Length; + int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + long innerStreamLength = this.innerStream.Length; - // Keep reading data until we've reached the end of the stream or filled the buffer - while (this.currentDataRemaining == 0 && bytesRead < count) + // Keep reading data until we've reached the end of the stream or filled the buffer. + int bytesRead = 0; + offset += totalBytesRead; + while (this.currentDataRemaining == 0 && totalBytesRead < count) { this.currentDataRemaining = this.getData(); if (this.currentDataRemaining == 0) { - return bytesRead; + return totalBytesRead; } offset += bytesRead; - if (offset >= length || offset >= count) + if (offset >= innerStreamLength || offset >= count) { - return bytesRead; + return totalBytesRead; } - bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); + bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; - bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); + bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + totalBytesRead += bytesRead; } - return bytesRead; + return totalBytesRead; } /// @@ -191,7 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (disposing) { - // dispose managed resources + // Dispose managed resources. if (this.CompressedStream != null) { this.CompressedStream.Dispose(); diff --git a/src/ImageSharp/Formats/README.md b/src/ImageSharp/Formats/README.md new file mode 100644 index 0000000000..4a2b401b1d --- /dev/null +++ b/src/ImageSharp/Formats/README.md @@ -0,0 +1,6 @@ +# Encoder/Decoder for true vision targa files + +Useful links for reference: + +- [FileFront](https://www.fileformat.info/format/tga/egff.htm) +- [Tga Specification](http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf) diff --git a/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs new file mode 100644 index 0000000000..e99e8b0c8d --- /dev/null +++ b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// The options for decoding tga images. Currently empty, but this may change in the future. + /// + internal interface ITgaDecoderOptions + { + } +} diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs new file mode 100644 index 0000000000..49983d2369 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Configuration options for use during tga encoding. + /// + internal interface ITgaEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + TgaBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets a value indicating whether run length compression should be used. + /// + TgaCompression Compression { get; } + } +} diff --git a/src/ImageSharp/Formats/Tga/ImageExtensions.cs b/src/ImageSharp/Formats/Tga/ImageExtensions.cs new file mode 100644 index 0000000000..286f04a226 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/ImageExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Tga; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsTga(this Image source, Stream stream) => SaveAsTga(source, stream, null); + + /// + /// Saves the image to the given stream with the tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) => + source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); + } +} diff --git a/src/ImageSharp/Formats/Tga/MetadataExtensions.cs b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs new file mode 100644 index 0000000000..2b0e405e75 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the tga format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static TgaMetadata GetTgaMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TgaFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Tga/TGA_Specification.pdf b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf new file mode 100644 index 0000000000..09c9a4ddda Binary files /dev/null and b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf differ diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs new file mode 100644 index 0000000000..a0666fa84d --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Enumerates the available bits per pixel the tga encoder supports. + /// + public enum TgaBitsPerPixel : byte + { + /// + /// 8 bits per pixel. Each pixel consists of 1 byte. + /// + Pixel8 = 8, + + /// + /// 16 bits per pixel. Each pixel consists of 2 bytes. + /// + Pixel16 = 16, + + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 24, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// + Pixel32 = 32 + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaCompression.cs b/src/ImageSharp/Formats/Tga/TgaCompression.cs new file mode 100644 index 0000000000..cc6e005eda --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaCompression.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Indicates if compression is used. + /// + public enum TgaCompression + { + /// + /// No compression is used. + /// + None, + + /// + /// Run length encoding is used. + /// + RunLength, + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs new file mode 100644 index 0000000000..18fbf4acd0 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the tga format. + /// + public sealed class TgaConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder()); + configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaConstants.cs b/src/ImageSharp/Formats/Tga/TgaConstants.cs new file mode 100644 index 0000000000..5aabe92a1d --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaConstants.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + internal static class TgaConstants + { + /// + /// The list of mimetypes that equate to a targa file. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-tga", "image/x-targa" }; + + /// + /// The list of file extensions that equate to a targa file. + /// + public static readonly IEnumerable FileExtensions = new[] { "tga", "vda", "icb", "vst" }; + + /// + /// The file header length of a tga image in bytes. + /// + public const int FileHeaderLength = 18; + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs new file mode 100644 index 0000000000..64dbdf58a9 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Image decoder for Truevision TGA images. + /// + public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector + { + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TgaDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + TgaThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + + // Not reachable, as the previous statement will throw a exception. + return null; + } + } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return new TgaDecoderCore(configuration, this).Identify(stream); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs new file mode 100644 index 0000000000..057ec1bfc7 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -0,0 +1,912 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Performs the tga decoding operation. + /// + internal sealed class TgaDecoderCore + { + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBuffer = new byte[4]; + + /// + /// The metadata. + /// + private ImageMetadata metadata; + + /// + /// The tga specific metadata. + /// + private TgaMetadata tgaMetadata; + + /// + /// The file header containing general information about the image. + /// + private TgaFileHeader fileHeader; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The bitmap decoder options. + /// + private readonly ITgaDecoderOptions options; + + /// + /// Indicates whether there is a alpha channel present. + /// + private bool hasAlpha; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options) + { + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } + + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); + + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The stream, where the image should be decoded from. Cannot be null. + /// + /// is null. + /// + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : unmanaged, IPixel + { + try + { + TgaImageOrigin origin = this.ReadFileHeader(stream); + this.currentStream.Skip(this.fileHeader.IdLength); + + // Parse the color map, if present. + if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1) + { + TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); + } + + if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0) + { + throw new UnknownImageFormatException("Width or height cannot be 0"); + } + + var image = Image.CreateUninitialized(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + if (this.fileHeader.ColorMapType == 1) + { + if (this.fileHeader.CMapLength <= 0) + { + TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map length"); + } + + if (this.fileHeader.CMapDepth <= 0) + { + TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map depth"); + } + + int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; + int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; + using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean)) + { + this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes); + + if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) + { + this.ReadPalettedRle( + this.fileHeader.Width, + this.fileHeader.Height, + pixels, + palette.Array, + colorMapPixelSizeInBytes, + origin); + } + else + { + this.ReadPaletted( + this.fileHeader.Width, + this.fileHeader.Height, + pixels, + palette.Array, + colorMapPixelSizeInBytes, + origin); + } + } + + return image; + } + + // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes. + if (this.fileHeader.CMapLength > 0) + { + int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; + this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); + } + + switch (this.fileHeader.PixelDepth) + { + case 8: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); + } + else + { + this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + case 15: + case 16: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); + } + else + { + this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + case 24: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); + } + else + { + this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + case 32: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); + } + else + { + this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } + + break; + + default: + TgaThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of tga files."); + break; + } + + return image; + } + catch (IndexOutOfRangeException e) + { + throw new ImageFormatException("TGA image does not have a valid format.", e); + } + } + + /// + /// Reads a uncompressed TGA image with a palette. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The color palette. + /// Color map size of one entry in bytes. + /// The image origin. + private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + + switch (colorMapPixelSizeInBytes) + { + case 2: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + + break; + + case 3: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + + break; + + case 4: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + } + } + + break; + } + } + } + + /// + /// Reads a run length encoded TGA image with a palette. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The color palette. + /// Color map size of one entry in bytes. + /// The image origin. + private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + int bytesPerPixel = 1; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + { + TPixel color = default; + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); + + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + int rowStartIdx = y * width * bytesPerPixel; + for (int x = 0; x < width; x++) + { + int idx = rowStartIdx + x; + switch (colorMapPixelSizeInBytes) + { + case 1: + color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 2: + this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); + break; + case 3: + color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 4: + color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; + } + } + } + } + + /// + /// Reads a uncompressed monochrome TGA image. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// the image origin. + private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + bool invertX = InvertX(origin); + if (invertX) + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = width - 1; x >= 0; x--) + { + this.ReadL8Pixel(color, x, pixelSpan); + } + } + + return; + } + + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) + { + bool invertY = InvertY(origin); + if (invertY) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadL8Row(width, pixels, row, y); + } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadL8Row(width, pixels, row, y); + } + } + } + } + + /// + /// Reads a uncompressed TGA image where each pixels has 16 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) + { + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.currentStream.Read(this.scratchBuffer, 0, 2); + if (!this.hasAlpha) + { + this.scratchBuffer[1] |= 1 << 7; + } + + if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) + { + color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); + } + else + { + color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); + } + + pixelSpan[x] = color; + } + } + else + { + this.currentStream.Read(row); + Span rowSpan = row.GetSpan(); + + if (!this.hasAlpha) + { + // We need to set the alpha component value to fully opaque. + for (int x = 1; x < rowSpan.Length; x += 2) + { + rowSpan[x] |= 1 << 7; + } + } + + if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) + { + PixelOperations.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); + } + else + { + PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); + } + } + } + } + } + + /// + /// Reads a uncompressed TGA image where each pixels has 24 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + bool invertX = InvertX(origin); + if (invertX) + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = width - 1; x >= 0; x--) + { + this.ReadBgr24Pixel(color, x, pixelSpan); + } + } + + return; + } + + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) + { + bool invertY = InvertY(origin); + + if (invertY) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadBgr24Row(width, pixels, row, y); + } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadBgr24Row(width, pixels, row, y); + } + } + } + } + + /// + /// Reads a uncompressed TGA image where each pixels has 32 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) + { + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) + { + if (InvertY(origin)) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadBgra32Row(width, pixels, row, y); + } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadBgra32Row(width, pixels, row, y); + } + } + } + + return; + } + + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + if (invertX) + { + for (int x = width - 1; x >= 0; x--) + { + this.ReadBgra32Pixel(x, color, pixelRow); + } + } + else + { + for (int x = 0; x < width; x++) + { + this.ReadBgra32Pixel(x, color, pixelRow); + } + } + } + } + + /// + /// Reads a run length encoded TGA image. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The bytes per pixel. + /// The image origin. + private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + var alphaBits = this.tgaMetadata.AlphaChannelBits; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + { + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(width, height, bufferSpan, bytesPerPixel); + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.GetRowSpan(newY); + int rowStartIdx = y * width * bytesPerPixel; + for (int x = 0; x < width; x++) + { + int idx = rowStartIdx + (x * bytesPerPixel); + switch (bytesPerPixel) + { + case 1: + color.FromL8(Unsafe.As(ref bufferSpan[idx])); + break; + case 2: + if (!this.hasAlpha) + { + // Set alpha value to 1, to treat it as opaque for Bgra5551. + bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); + } + + if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) + { + color.FromLa16(Unsafe.As(ref bufferSpan[idx])); + } + else + { + color.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); + } + + break; + case 3: + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + break; + case 4: + if (this.hasAlpha) + { + color.FromBgra32(Unsafe.As(ref bufferSpan[idx])); + } + else + { + var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; + color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); + } + + break; + } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; + } + } + } + } + + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) + { + this.ReadFileHeader(stream); + return new ImageInfo( + new PixelTypeInfo(this.fileHeader.PixelDepth), + this.fileHeader.Width, + this.fileHeader.Height, + this.metadata); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadL8Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadL8Pixel(TPixel color, int x, Span pixelSpan) + where TPixel : unmanaged, IPixel + { + var pixelValue = (byte)this.currentStream.ReadByte(); + color.FromL8(Unsafe.As(ref pixelValue)); + pixelSpan[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(this.scratchBuffer, 0, 3); + color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0])); + pixelSpan[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgr24Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(this.scratchBuffer, 0, 4); + var alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; + color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgra32Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + where TPixel : unmanaged, IPixel + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(byte[] palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) + where TPixel : unmanaged, IPixel + { + Bgra5551 bgra = default; + bgra.FromBgra5551(Unsafe.As(ref palette[index * colorMapPixelSizeInBytes])); + + if (!this.hasAlpha) + { + // Set alpha value to 1, to treat it as opaque. + bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); + } + + color.FromBgra5551(bgra); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgr24Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra32Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + pixelRow[x] = color; + } + + /// + /// Produce uncompressed tga data from a run length encoded stream. + /// + /// The width of the image. + /// The height of the image. + /// Buffer for uncompressed data. + /// The bytes used per pixel. + private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel) + { + int uncompressedPixels = 0; + var pixel = new byte[bytesPerPixel]; + int totalPixels = width * height; + while (uncompressedPixels < totalPixels) + { + byte runLengthByte = (byte)this.currentStream.ReadByte(); + + // The high bit of a run length packet is set to 1. + int highBit = runLengthByte >> 7; + if (highBit == 1) + { + int runLength = runLengthByte & 127; + this.currentStream.Read(pixel, 0, bytesPerPixel); + int bufferIdx = uncompressedPixels * bytesPerPixel; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) + { + pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx)); + bufferIdx += bytesPerPixel; + } + } + else + { + // Non-run-length encoded packet. + int runLength = runLengthByte; + int bufferIdx = uncompressedPixels * bytesPerPixel; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) + { + this.currentStream.Read(pixel, 0, bytesPerPixel); + pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx)); + bufferIdx += bytesPerPixel; + } + } + } + } + + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the image. + /// The image origin. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InvertY(int y, int height, TgaImageOrigin origin) + { + if (InvertY(origin)) + { + return height - y - 1; + } + + return y; + } + + /// + /// Indicates whether the y coordinates needs to be inverted, to keep a top left origin. + /// + /// The image origin. + /// True, if y coordinate needs to be inverted. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool InvertY(TgaImageOrigin origin) + { + switch (origin) + { + case TgaImageOrigin.BottomLeft: + case TgaImageOrigin.BottomRight: + return true; + default: + return false; + } + } + + /// + /// Returns the x- value based on the given width. + /// + /// The x- value representing the current column. + /// The width of the image. + /// The image origin. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InvertX(int x, int width, TgaImageOrigin origin) + { + if (InvertX(origin)) + { + return width - x - 1; + } + + return x; + } + + /// + /// Indicates whether the x coordinates needs to be inverted, to keep a top left origin. + /// + /// The image origin. + /// True, if x coordinate needs to be inverted. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool InvertX(TgaImageOrigin origin) + { + switch (origin) + { + case TgaImageOrigin.TopRight: + case TgaImageOrigin.BottomRight: + return true; + default: + return false; + } + } + + /// + /// Reads the tga file header from the stream. + /// + /// The containing image data. + /// The image origin. + private TgaImageOrigin ReadFileHeader(Stream stream) + { + this.currentStream = stream; + + Span buffer = stackalloc byte[TgaFileHeader.Size]; + + this.currentStream.Read(buffer, 0, TgaFileHeader.Size); + this.fileHeader = TgaFileHeader.Parse(buffer); + this.metadata = new ImageMetadata(); + this.tgaMetadata = this.metadata.GetTgaMetadata(); + this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; + + var alphaBits = this.fileHeader.ImageDescriptor & 0xf; + if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8) + { + TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits"); + } + + this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; + this.hasAlpha = alphaBits > 0; + + // Bits 4 and 5 describe the image origin. + var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); + return origin; + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs new file mode 100644 index 0000000000..e938067a1c --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Image encoder for writing an image to a stream as a targa truevision image. + /// + public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions + { + /// + /// Gets or sets the number of bits per pixel. + /// + public TgaBitsPerPixel? BitsPerPixel { get; set; } + + /// + /// Gets or sets a value indicating whether no compression or run length compression should be used. + /// + public TgaCompression Compression { get; set; } = TgaCompression.RunLength; + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs new file mode 100644 index 0000000000..94bd367aa0 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -0,0 +1,369 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Image encoder for writing an image to a stream as a truevision targa image. + /// + internal sealed class TgaEncoderCore + { + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// Reusable buffer for writing data. + /// + private readonly byte[] buffer = new byte[2]; + + /// + /// The color depth, in number of bits per pixel. + /// + private TgaBitsPerPixel? bitsPerPixel; + + /// + /// Indicates if run length compression should be used. + /// + private readonly TgaCompression compression; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.bitsPerPixel = options.BitsPerPixel; + this.compression = options.Compression; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); + this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel; + + TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; + if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) + { + imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite; + } + + byte imageDescriptor = 0; + if (this.compression is TgaCompression.RunLength) + { + // If compression is used, set bit 5 of the image descriptor to indicate a left top origin. + imageDescriptor |= 0x20; + } + + if (this.bitsPerPixel is TgaBitsPerPixel.Pixel32) + { + // Indicate, that 8 bit are used for the alpha channel. + imageDescriptor |= 0x8; + } + + if (this.bitsPerPixel is TgaBitsPerPixel.Pixel16) + { + // Indicate, that 1 bit is used for the alpha channel. + imageDescriptor |= 0x1; + } + + var fileHeader = new TgaFileHeader( + idLength: 0, + colorMapType: 0, + imageType: imageType, + cMapStart: 0, + cMapLength: 0, + cMapDepth: 0, + xOffset: 0, + yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left. + width: (short)image.Width, + height: (short)image.Height, + pixelDepth: (byte)this.bitsPerPixel.Value, + imageDescriptor: imageDescriptor); + + Span buffer = stackalloc byte[TgaFileHeader.Size]; + fileHeader.WriteTo(buffer); + + stream.Write(buffer, 0, TgaFileHeader.Size); + + if (this.compression is TgaCompression.RunLength) + { + this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); + } + else + { + this.WriteImage(stream, image.Frames.RootFrame); + } + + stream.Flush(); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WriteImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + Buffer2D pixels = image.PixelBuffer; + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + this.Write8Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel16: + this.Write16Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel24: + this.Write24Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel32: + this.Write32Bit(stream, pixels); + break; + } + } + + /// + /// Writes a run length encoded tga image to the stream. + /// + /// The pixel type. + /// The stream to write the image to. + /// The image to encode. + private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + Rgba32 color = default; + Buffer2D pixels = image.PixelBuffer; + int totalPixels = image.Width * image.Height; + int encodedPixels = 0; + while (encodedPixels < totalPixels) + { + int x = encodedPixels % pixels.Width; + int y = encodedPixels / pixels.Width; + TPixel currentPixel = pixels[x, y]; + currentPixel.ToRgba32(ref color); + byte equalPixelCount = this.FindEqualPixels(pixels, x, y); + + // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. + stream.WriteByte((byte)(equalPixelCount | 128)); + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + int luminance = GetLuminance(currentPixel); + stream.WriteByte((byte)luminance); + break; + + case TgaBitsPerPixel.Pixel16: + var bgra5551 = new Bgra5551(color.ToVector4()); + BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); + stream.WriteByte(this.buffer[0]); + stream.WriteByte(this.buffer[1]); + + break; + + case TgaBitsPerPixel.Pixel24: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + break; + + case TgaBitsPerPixel.Pixel32: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + stream.WriteByte(color.A); + break; + } + + encodedPixels += equalPixelCount + 1; + } + } + + /// + /// Finds consecutive pixels which have the same value. + /// + /// The pixel type. + /// The pixels of the image. + /// X coordinate to start searching for the same pixels. + /// Y coordinate to start searching for the same pixels. + /// The number of equal pixels. + private byte FindEqualPixels(Buffer2D pixels, int xStart, int yStart) + where TPixel : unmanaged, IPixel + { + byte equalPixelCount = 0; + bool firstRow = true; + TPixel startPixel = pixels[xStart, yStart]; + for (int y = yStart; y < pixels.Height; y++) + { + for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++) + { + TPixel nextPixel = pixels[x, y]; + if (startPixel.Equals(nextPixel)) + { + equalPixelCount++; + } + else + { + return equalPixelCount; + } + + if (equalPixelCount >= 127) + { + return equalPixelCount; + } + } + + firstRow = false; + } + + return equalPixelCount; + } + + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); + + /// + /// Writes the 8bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write8Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToL8Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Writes the 16bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write16Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Writes the 24bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write24Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Writes the 32bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write32Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } + } + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from. + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel) + where TPixel : unmanaged, IPixel + { + var vector = sourcePixel.ToVector4(); + return ImageMaths.GetBT709Luminance(ref vector, 256); + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs new file mode 100644 index 0000000000..e2bbb6fbd2 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// This block of bytes tells the application detailed information about the targa image. + /// + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal readonly struct TgaFileHeader + { + /// + /// Defines the size of the data structure in the targa file. + /// + public const int Size = TgaConstants.FileHeaderLength; + + public TgaFileHeader( + byte idLength, + byte colorMapType, + TgaImageType imageType, + short cMapStart, + short cMapLength, + byte cMapDepth, + short xOffset, + short yOffset, + short width, + short height, + byte pixelDepth, + byte imageDescriptor) + { + this.IdLength = idLength; + this.ColorMapType = colorMapType; + this.ImageType = imageType; + this.CMapStart = cMapStart; + this.CMapLength = cMapLength; + this.CMapDepth = cMapDepth; + this.XOffset = xOffset; + this.YOffset = yOffset; + this.Width = width; + this.Height = height; + this.PixelDepth = pixelDepth; + this.ImageDescriptor = imageDescriptor; + } + + /// + /// Gets the id length. + /// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number + /// of characters is 255. A value of zero indicates that no Image ID field is included with the image. + /// + public byte IdLength { get; } + + /// + /// Gets the color map type. + /// This field indicates the type of color map (if any) included with the image. There are currently 2 defined + /// values for this field: + /// 0 - indicates that no color-map data is included with this image. + /// 1 - indicates that a color-map is included with this image. + /// + public byte ColorMapType { get; } + + /// + /// Gets the image type. + /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various + /// pixel depths. + /// + public TgaImageType ImageType { get; } + + /// + /// Gets the start of the color map. + /// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field + /// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero. + /// + public short CMapStart { get; } + + /// + /// Gets the total number of color map entries included. + /// + public short CMapLength { get; } + + /// + /// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. + /// + public byte CMapDepth { get; } + + /// + /// Gets the XOffset. + /// These bytes specify the absolute horizontal coordinate for the lower left + /// corner of the image as it is positioned on a display device having an + /// origin at the lower left of the screen. + /// + public short XOffset { get; } + + /// + /// Gets the YOffset. + /// These bytes specify the absolute vertical coordinate for the lower left + /// corner of the image as it is positioned on a display device having an + /// origin at the lower left of the screen. + /// + public short YOffset { get; } + + /// + /// Gets the width of the image in pixels. + /// + public short Width { get; } + + /// + /// Gets the height of the image in pixels. + /// + public short Height { get; } + + /// + /// Gets the number of bits per pixel. This number includes + /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and + /// 32 but other pixel depths could be used. + /// + public byte PixelDepth { get; } + + /// + /// Gets the ImageDescriptor. + /// ImageDescriptor contains two pieces of information. + /// Bits 0 through 3 contain the number of attribute bits per pixel. + /// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel, + /// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image. + /// This position may be any of the four corners of the display screen. + /// When both of these bits are set to zero, the image origin is the lower-left corner of the screen. + /// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0. + /// + public byte ImageDescriptor { get; } + + public static TgaFileHeader Parse(Span data) + { + return MemoryMarshal.Cast(data)[0]; + } + + public void WriteTo(Span buffer) + { + ref TgaFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + dest = this; + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaFormat.cs b/src/ImageSharp/Formats/Tga/TgaFormat.cs new file mode 100644 index 0000000000..badb1d77a4 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaFormat.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the tga format. + /// + public sealed class TgaFormat : IImageFormat + { + /// + /// Gets the current instance. + /// + public static TgaFormat Instance { get; } = new TgaFormat(); + + /// + public string Name => "TGA"; + + /// + public string DefaultMimeType => "image/tga"; + + /// + public IEnumerable MimeTypes => TgaConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => TgaConstants.FileExtensions; + + /// + public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata(); + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs new file mode 100644 index 0000000000..af40c333b6 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Detects tga file headers. + /// + public sealed class TgaImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => TgaConstants.FileHeaderLength; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) + { + // There are no magic bytes in a tga file, so at least the image type + // and the colormap type in the header will be checked for a valid value. + if (header[1] != 0 && header[1] != 1) + { + return false; + } + + var imageType = (TgaImageType)header[2]; + return imageType.IsValid(); + } + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs new file mode 100644 index 0000000000..06d7b59455 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + internal enum TgaImageOrigin + { + /// + /// Bottom left origin. + /// + BottomLeft = 0, + + /// + /// Bottom right origin. + /// + BottomRight = 1, + + /// + /// Top left origin. + /// + TopLeft = 2, + + /// + /// Top right origin. + /// + TopRight = 3, + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaImageType.cs b/src/ImageSharp/Formats/Tga/TgaImageType.cs new file mode 100644 index 0000000000..491fd3ea77 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageType.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors. + ImageSharp.Formats.Tga +{ + /// + /// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color, + /// True-Color and Direct-Color images of various pixel depths. + /// + public enum TgaImageType : byte + { + /// + /// No image data included. + /// + NoImageData = 0, + + /// + /// Uncompressed, color mapped image. + /// + ColorMapped = 1, + + /// + /// Uncompressed true color image. + /// + TrueColor = 2, + + /// + /// Uncompressed Black and white (grayscale) image. + /// + BlackAndWhite = 3, + + /// + /// Run length encoded, color mapped image. + /// + RleColorMapped = 9, + + /// + /// Run length encoded, true color image. + /// + RleTrueColor = 10, + + /// + /// Run length encoded, black and white (grayscale) image. + /// + RleBlackAndWhite = 11, + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs new file mode 100644 index 0000000000..6a30cdddd7 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Extension methods for TgaImageType enum. + /// + public static class TgaImageTypeExtensions + { + /// + /// Checks if this tga image type is run length encoded. + /// + /// The tga image type. + /// True, if this image type is run length encoded, otherwise false. + public static bool IsRunLengthEncoded(this TgaImageType imageType) + { + if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor) + { + return true; + } + + return false; + } + + /// + /// Checks, if the image type has valid value. + /// + /// The image type. + /// true, if its a valid tga image type. + public static bool IsValid(this TgaImageType imageType) + { + switch (imageType) + { + case TgaImageType.NoImageData: + case TgaImageType.ColorMapped: + case TgaImageType.TrueColor: + case TgaImageType.BlackAndWhite: + case TgaImageType.RleColorMapped: + case TgaImageType.RleTrueColor: + case TgaImageType.RleBlackAndWhite: + return true; + + default: + return false; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs new file mode 100644 index 0000000000..69dee768a9 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Provides TGA specific metadata information for the image. + /// + public class TgaMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public TgaMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private TgaMetadata(TgaMetadata other) + { + this.BitsPerPixel = other.BitsPerPixel; + } + + /// + /// Gets or sets the number of bits per pixel. + /// + public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24; + + /// + /// Gets or sets the the number of alpha bits per pixel. + /// + public byte AlphaChannelBits { get; set; } = 0; + + /// + public IDeepCloneable DeepClone() => new TgaMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs new file mode 100644 index 0000000000..fc158e781e --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Tga +{ + internal static class TgaThrowHelper + { + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); + + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) + => throw new InvalidImageContentException(errorMessage, innerException); + + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedException(string errorMessage) + => throw new NotSupportedException(errorMessage); + } +} diff --git a/src/ImageSharp/GeometryUtilities.cs b/src/ImageSharp/GeometryUtilities.cs new file mode 100644 index 0000000000..00fa5b97f1 --- /dev/null +++ b/src/ImageSharp/GeometryUtilities.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility class for common geometric functions. + /// + public static class GeometryUtilities + { + /// + /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. + /// + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreeToRadian(float degree) => degree * (MathF.PI / 180F); + + /// + /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. + /// + /// The angle in radians. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RadianToDegree(float radian) => radian / (MathF.PI / 180F); + } +} diff --git a/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs new file mode 100644 index 0000000000..c8beea8e85 --- /dev/null +++ b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp +{ + /// + /// Adds extensions that allow the processing of images to the type. + /// + public static class GraphicOptionsDefaultsExtensions + { + /// + /// Sets the default options against the image processing context. + /// + /// The image processing context to store default against. + /// The action to update instance of the default options used. + /// The passed in to allow chaining. + public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, Action optionsBuilder) + { + var cloned = context.GetGraphicsOptions().DeepClone(); + optionsBuilder(cloned); + context.Properties[typeof(GraphicsOptions)] = cloned; + return context; + } + + /// + /// Sets the default options against the configuration. + /// + /// The configuration to store default against. + /// The default options to use. + public static void SetGraphicsOptions(this Configuration configuration, Action optionsBuilder) + { + var cloned = configuration.GetGraphicsOptions().DeepClone(); + optionsBuilder(cloned); + configuration.Properties[typeof(GraphicsOptions)] = cloned; + } + + /// + /// Sets the default options against the image processing context. + /// + /// The image processing context to store default against. + /// The default options to use. + /// The passed in to allow chaining. + public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, GraphicsOptions options) + { + context.Properties[typeof(GraphicsOptions)] = options; + return context; + } + + /// + /// Sets the default options against the configuration. + /// + /// The configuration to store default against. + /// The default options to use. + public static void SetGraphicsOptions(this Configuration configuration, GraphicsOptions options) + { + configuration.Properties[typeof(GraphicsOptions)] = options; + } + + /// + /// Gets the default options against the image processing context. + /// + /// The image processing context to retrieve defaults from. + /// The globaly configued default options. + public static GraphicsOptions GetGraphicsOptions(this IImageProcessingContext context) + { + if (context.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go) + { + return go; + } + + var configOptions = context.Configuration.GetGraphicsOptions(); + + // do not cache the fall back to config into the the processing context + // in case someone want to change the value on the config and expects it re trflow thru + return configOptions; + } + + /// + /// Gets the default options against the image processing context. + /// + /// The configuration to retrieve defaults from. + /// The globaly configued default options. + public static GraphicsOptions GetGraphicsOptions(this Configuration configuration) + { + if (configuration.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go) + { + return go; + } + + var configOptions = new GraphicsOptions(); + + // capture the fallback so the same instance will always be returned in case its mutated + configuration.Properties[typeof(GraphicsOptions)] = configOptions; + return configOptions; + } + } +} diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index 214b10810a..47b930e654 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -8,170 +8,82 @@ namespace SixLabors.ImageSharp /// /// Options for influencing the drawing functions. /// - public struct GraphicsOptions + public class GraphicsOptions : IDeepCloneable { - /// - /// Represents the default . - /// - public static readonly GraphicsOptions Default = new GraphicsOptions(true); - - private float? blendPercentage; - - private int? antialiasSubpixelDepth; - - private bool? antialias; - - private PixelColorBlendingMode colorBlendingMode; - - private PixelAlphaCompositionMode alphaCompositionMode; - - /// - /// Initializes a new instance of the struct. - /// - /// If set to true [enable antialiasing]. - public GraphicsOptions(bool enableAntialiasing) - { - this.colorBlendingMode = PixelColorBlendingMode.Normal; - this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; - this.blendPercentage = 1; - this.antialiasSubpixelDepth = 16; - this.antialias = enableAntialiasing; - } - - /// - /// Initializes a new instance of the struct. - /// - /// If set to true [enable antialiasing]. - /// blending percentage to apply to the drawing operation - public GraphicsOptions(bool enableAntialiasing, float opacity) - { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - - this.colorBlendingMode = PixelColorBlendingMode.Normal; - this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; - this.blendPercentage = opacity; - this.antialiasSubpixelDepth = 16; - this.antialias = enableAntialiasing; - } + private int antialiasSubpixelDepth = 16; + private float blendPercentage = 1F; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the class. /// - /// If set to true [enable antialiasing]. - /// blending percentage to apply to the drawing operation - /// color blending mode to apply to the drawing operation - public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, float opacity) + public GraphicsOptions() { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - - this.colorBlendingMode = blending; - this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; - this.blendPercentage = opacity; - this.antialiasSubpixelDepth = 16; - this.antialias = enableAntialiasing; } - /// - /// Initializes a new instance of the struct. - /// - /// If set to true [enable antialiasing]. - /// blending percentage to apply to the drawing operation - /// color blending mode to apply to the drawing operation - /// alpha composition mode to apply to the drawing operation - public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, PixelAlphaCompositionMode composition, float opacity) + private GraphicsOptions(GraphicsOptions source) { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - - this.colorBlendingMode = blending; - this.alphaCompositionMode = composition; - this.blendPercentage = opacity; - this.antialiasSubpixelDepth = 16; - this.antialias = enableAntialiasing; + this.AlphaCompositionMode = source.AlphaCompositionMode; + this.Antialias = source.Antialias; + this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; + this.BlendPercentage = source.BlendPercentage; + this.ColorBlendingMode = source.ColorBlendingMode; } /// /// Gets or sets a value indicating whether antialiasing should be applied. + /// Defaults to true. /// - public bool Antialias - { - get => this.antialias ?? true; - set => this.antialias = value; - } + public bool Antialias { get; set; } = true; /// /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. + /// Defaults to 16. /// public int AntialiasSubpixelDepth { - get => this.antialiasSubpixelDepth ?? 16; - set => this.antialiasSubpixelDepth = value; + get + { + return this.antialiasSubpixelDepth; + } + + set + { + Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); + this.antialiasSubpixelDepth = value; + } } /// - /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation. + /// Range 0..1; Defaults to 1. /// public float BlendPercentage { - get => (this.blendPercentage ?? 1).Clamp(0, 1); - set => this.blendPercentage = value; - } + get + { + return this.blendPercentage; + } - // In the future we could expose a PixelBlender directly on here - // or some forms of PixelBlender factory for each pixel type. Will need - // some API thought post V1. + set + { + Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); + this.blendPercentage = value; + } + } /// - /// Gets or sets a value indicating the color blending mode to apply to the drawing operation + /// Gets or sets a value indicating the color blending mode to apply to the drawing operation. + /// Defaults to . /// - public PixelColorBlendingMode ColorBlendingMode - { - get => this.colorBlendingMode; - set => this.colorBlendingMode = value; - } + public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal; /// /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation + /// Defaults to . /// - public PixelAlphaCompositionMode AlphaCompositionMode - { - get => this.alphaCompositionMode; - set => this.alphaCompositionMode = value; - } + public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; - /// - /// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings. - /// - /// the color - /// true if the color can be considered opaque - /// - /// Blending and composition is an expensive operation, in some cases, like - /// filling with a solid color, the blending can be avoided by a plain color replacement. - /// This method can be useful for such processors to select the fast path. - /// - internal bool IsOpaqueColorWithoutBlending(Color color) - { - if (this.ColorBlendingMode != PixelColorBlendingMode.Normal) - { - return false; - } - - if (this.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver && - this.AlphaCompositionMode != PixelAlphaCompositionMode.Src) - { - return false; - } - - if (this.BlendPercentage != 1f) - { - return false; - } - - if (color.ToVector4().W != 1f) - { - return false; - } - - return true; - } + /// + public GraphicsOptions DeepClone() => new GraphicsOptions(this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs index 07f8928068..0345717d21 100644 --- a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs +++ b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.IO { diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 8d0df599ea..c28a214525 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -1,13 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { @@ -32,11 +32,11 @@ namespace SixLabors.ImageSharp int width, int height, ImageMetadata metadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata); + return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); } /// @@ -47,19 +47,26 @@ namespace SixLabors.ImageSharp /// The mime type or null if none found. private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) { - // This is probably a candidate for making into a public API in the future! - int maxHeaderSize = config.MaxHeaderSize; - if (maxHeaderSize <= 0) + // We take a minimum of the stream length vs the max header size and always check below + // to ensure that only formats that headers fit within the given buffer length are tested. + int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length); + if (headerSize <= 0) { return null; } - using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean)) + using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean)) { long startPosition = stream.Position; - stream.Read(buffer.Array, 0, maxHeaderSize); + stream.Read(buffer.Array, 0, headerSize); stream.Position = startPosition; - return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); + + // Does the given stream contain enough data to fit in the header for the format + // and does that data match the format specification? + // Individual formats should still check since they are public. + return config.ImageFormatsManager.FormatDetectors + .Where(x => x.HeaderSize <= headerSize) + .Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); } } @@ -91,7 +98,7 @@ namespace SixLabors.ImageSharp /// private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -123,10 +130,14 @@ namespace SixLabors.ImageSharp /// /// The or null if suitable info detector not found. /// - private static IImageInfo InternalIdentity(Stream stream, Configuration config) + private static (IImageInfo info, IImageFormat format) InternalIdentity(Stream stream, Configuration config) { - var detector = DiscoverDecoder(stream, config, out IImageFormat _) as IImageInfoDetector; - return detector?.Identify(config, stream); + if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector)) + { + return (null, null); + } + + return (detector?.Identify(config, stream), format); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 465139b2e5..a0e8097d8e 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -17,23 +17,71 @@ namespace SixLabors.ImageSharp /// By reading the header on the provided byte array this calculates the images format. /// /// The byte array containing encoded image data to read the header from. + /// The data is null. /// The format or null if none found. public static IImageFormat DetectFormat(byte[] data) - { - return DetectFormat(Configuration.Default, data); - } + => DetectFormat(Configuration.Default, data); /// /// By reading the header on the provided byte array this calculates the images format. /// - /// The configuration. + /// The configuration. /// The byte array containing encoded image data to read the header from. + /// The configuration is null. + /// The data is null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, byte[] data) + public static IImageFormat DetectFormat(Configuration configuration, byte[] data) { - using (var stream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return DetectFormat(config, stream); + return DetectFormat(configuration, stream); + } + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte array containing encoded image data to read the header from. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(byte[] data, out IImageFormat format) => Identify(Configuration.Default, data, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format) + { + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) + { + return Identify(configuration, stream, out format); } } @@ -41,17 +89,23 @@ namespace SixLabors.ImageSharp /// Load a new instance of from the given encoded byte array. /// /// The byte array containing image data. + /// The configuration is null. + /// The data is null. /// A new . - public static Image Load(byte[] data) => Load(Configuration.Default, data); + public static Image Load(byte[] data) + => Load(Configuration.Default, data); /// /// Load a new instance of from the given encoded byte array. /// /// The byte array containing encoded image data. /// The pixel format. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(byte[] data) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data); /// @@ -60,41 +114,56 @@ namespace SixLabors.ImageSharp /// The byte array containing image data. /// The mime type of the decoded image. /// The pixel format. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(byte[] data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, out format); /// /// Load a new instance of from the given encoded byte array. /// - /// The configuration options. + /// The configuration options. /// The byte array containing encoded image data. /// The pixel format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, byte[] data) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data) + where TPixel : unmanaged, IPixel { - using (var stream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream); + return Load(configuration, stream); } } /// /// Load a new instance of from the given encoded byte array. /// - /// The configuration options. + /// The configuration options. /// The byte array containing encoded image data. /// The of the decoded image. /// The pixel format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, byte[] data, out IImageFormat format) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) + where TPixel : unmanaged, IPixel { - using (var stream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -104,11 +173,16 @@ namespace SixLabors.ImageSharp /// The byte array containing encoded image data. /// The decoder. /// The pixel format. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(byte[] data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var stream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { return Load(stream, decoder); } @@ -117,24 +191,30 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte array. /// - /// The Configuration. + /// The Configuration. /// The byte array containing encoded image data. /// The decoder. /// The pixel format. + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) + where TPixel : unmanaged, IPixel { - using (var memoryStream = new MemoryStream(data)) + Guard.NotNull(data, nameof(data)); + + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, memoryStream, decoder); + return Load(configuration, stream, decoder); } } /// - /// By reading the header on the provided byte array this calculates the images format. + /// By reading the header on the provided byte span this calculates the images format. /// - /// The byte array containing encoded image data to read the header from. + /// The byte span containing encoded image data to read the header from. /// The format or null if none found. public static IImageFormat DetectFormat(ReadOnlySpan data) { @@ -142,20 +222,23 @@ namespace SixLabors.ImageSharp } /// - /// By reading the header on the provided byte array this calculates the images format. + /// By reading the header on the provided byte span this calculates the images format. /// - /// The configuration. - /// The byte array containing encoded image data to read the header from. + /// The configuration. + /// The byte span containing encoded image data to read the header from. + /// The configuration is null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, ReadOnlySpan data) + public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan data) { - int maxHeaderSize = config.MaxHeaderSize; + Guard.NotNull(configuration, nameof(configuration)); + + int maxHeaderSize = configuration.MaxHeaderSize; if (maxHeaderSize <= 0) { return null; } - foreach (IImageFormatDetector detector in config.ImageFormatsManager.FormatDetectors) + foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors) { IImageFormat f = detector.DetectFormat(data); @@ -173,48 +256,57 @@ namespace SixLabors.ImageSharp /// /// The byte span containing encoded image data. /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(ReadOnlySpan data) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data); /// - /// Load a new instance of from the given encoded byte array. + /// Load a new instance of from the given encoded byte span. /// /// The byte span containing image data. /// The mime type of the decoded image. /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(ReadOnlySpan data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, out format); /// - /// Load a new instance of from the given encoded byte array. + /// Load a new instance of from the given encoded byte span. /// /// The byte span containing encoded image data. /// The decoder. /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static Image Load(ReadOnlySpan data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, decoder); /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing encoded image data. /// The pixel format. + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static unsafe Image Load(Configuration config, ReadOnlySpan data) - where TPixel : struct, IPixel + public static unsafe Image Load(Configuration configuration, ReadOnlySpan data) + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream); + return Load(configuration, stream); } } } @@ -222,22 +314,25 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The Configuration. + /// The Configuration. /// The byte span containing image data. /// The decoder. /// The pixel format. + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } @@ -245,22 +340,25 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. /// The of the decoded image. /// The pixel format. + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } } @@ -270,98 +368,135 @@ namespace SixLabors.ImageSharp /// /// The byte array containing image data. /// The detected format. - /// A new . - public static Image Load(byte[] data, out IImageFormat format) => - Load(Configuration.Default, data, out format); + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(byte[] data, out IImageFormat format) + => Load(Configuration.Default, data, out format); /// /// Load a new instance of from the given encoded byte array. /// /// The byte array containing encoded image data. /// The decoder. - /// A new . - public static Image Load(byte[] data, IImageDecoder decoder) => Load(Configuration.Default, data, decoder); + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(byte[] data, IImageDecoder decoder) + => Load(Configuration.Default, data, decoder); /// - /// Load a new instance of from the given encoded byte array. + /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing encoded image data. - /// A new . - public static Image Load(Configuration config, byte[] data) => Load(config, data, out _); + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, byte[] data) + => Load(configuration, data, out _); /// - /// Load a new instance of from the given encoded byte array. + /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing image data. /// The decoder. - /// A new . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) { - using (var stream = new MemoryStream(data)) + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } /// - /// Load a new instance of from the given encoded byte array. + /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing image data. /// The mime type of the decoded image. - /// A new . - public static Image Load(Configuration config, byte[] data, out IImageFormat format) + /// The configuration is null. + /// The data is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) { - using (var stream = new MemoryStream(data)) + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } /// - /// Load a new instance of from the given encoded byte span. + /// Load a new instance of from the given encoded byte span. /// /// The byte span containing image data. - /// A new . - public static Image Load(ReadOnlySpan data) => Load(Configuration.Default, data); + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(ReadOnlySpan data) + => Load(Configuration.Default, data); /// /// Load a new instance of from the given encoded byte span. /// /// The byte span containing image data. /// The decoder. - /// A new . - public static Image Load(ReadOnlySpan data, IImageDecoder decoder) => - Load(Configuration.Default, data, decoder); + /// The data is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(ReadOnlySpan data, IImageDecoder decoder) + => Load(Configuration.Default, data, decoder); /// /// Load a new instance of from the given encoded byte array. /// /// The byte span containing image data. /// The detected format. - /// A new . - public static Image Load(ReadOnlySpan data, out IImageFormat format) => - Load(Configuration.Default, data, out format); + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(ReadOnlySpan data, out IImageFormat format) + => Load(Configuration.Default, data, out format); /// /// Decodes a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. - /// A new . - public static Image Load(Configuration config, ReadOnlySpan data) => Load(config, data, out _); + /// The . + public static Image Load(Configuration configuration, ReadOnlySpan data) + => Load(configuration, data, out _); /// /// Load a new instance of from the given encoded byte span. /// - /// The Configuration. + /// The Configuration. /// The byte span containing image data. /// The decoder. - /// A new . + /// The configuration is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, IImageDecoder decoder) { @@ -369,7 +504,7 @@ namespace SixLabors.ImageSharp { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } @@ -377,12 +512,15 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. /// The of the decoded image.> - /// A new . + /// The configuration is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, out IImageFormat format) { @@ -390,7 +528,7 @@ namespace SixLabors.ImageSharp { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 08ed381c5a..8546dd2700 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,37 +19,78 @@ namespace SixLabors.ImageSharp /// The image file to open and to read the header from. /// The mime type or null if none found. public static IImageFormat DetectFormat(string filePath) - { - return DetectFormat(Configuration.Default, filePath); - } + => DetectFormat(Configuration.Default, filePath); /// /// By reading the header on the provided file this calculates the images mime type. /// - /// The configuration. + /// The configuration. /// The image file to open and to read the header from. + /// The configuration is null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, string filePath) + public static IImageFormat DetectFormat(Configuration configuration, string filePath) { - config = config ?? Configuration.Default; - using (Stream file = config.FileSystem.OpenRead(filePath)) + Guard.NotNull(configuration, nameof(configuration)); + + using (Stream file = configuration.FileSystem.OpenRead(filePath)) { - return DetectFormat(config, file); + return DetectFormat(configuration, file); } } /// - /// Create a new instance of the class from the given file. + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath) + => Identify(filePath, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The format type of the decoded image. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath, out IImageFormat format) + => Identify(Configuration.Default, filePath, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, string filePath, out IImageFormat format) + { + Guard.NotNull(configuration, nameof(configuration)); + using (Stream file = configuration.FileSystem.OpenRead(filePath)) + { + return Identify(configuration, file, out format); + } + } + + /// + /// Create a new instance of the class from the given file. /// /// The file path to the image. /// /// Thrown if the stream is not readable nor seekable. /// - /// A new . - public static Image Load(string path) => Load(Configuration.Default, path); + /// The . + public static Image Load(string path) + => Load(Configuration.Default, path); /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// /// The file path to the image. /// The mime type of the decoded image. @@ -57,115 +98,129 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, out IImageFormat format) => Load(Configuration.Default, path, out format); + public static Image Load(string path, out IImageFormat format) + => Load(Configuration.Default, path, out format); /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The file path to the image. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// A new . - public static Image Load(Configuration config, string path) => Load(config, path, out _); + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, string path) + => Load(configuration, path, out _); /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// - /// The Configuration. + /// The Configuration. /// The file path to the image. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// A new . - public static Image Load(Configuration config, string path, IImageDecoder decoder) + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Configuration configuration, string path, IImageDecoder decoder) { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } /// - /// Create a new instance of the class from the given file. + /// Create a new instance of the class from the given file. /// /// The file path to the image. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// A new . - public static Image Load(string path, IImageDecoder decoder) => Load(Configuration.Default, path, decoder); + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(string path, IImageDecoder decoder) + => Load(Configuration.Default, path, decoder); /// /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . public static Image Load(string path) - where TPixel : struct, IPixel - { - return Load(Configuration.Default, path); - } + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, path); /// /// Create a new instance of the class from the given file. /// /// The file path to the image. /// The mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . public static Image Load(string path, out IImageFormat format) - where TPixel : struct, IPixel - { - return Load(Configuration.Default, path, out format); - } + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, path, out format); /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration config, string path) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream); + return Load(configuration, stream); } } /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. /// The mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path, out IImageFormat format) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -173,18 +228,22 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given file. /// The pixel type is selected by the decoder. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. /// The mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) + public static Image Load(Configuration configuration, string path, out IImageFormat format) { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -193,35 +252,38 @@ namespace SixLabors.ImageSharp /// /// The file path to the image. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . public static Image Load(string path, IImageDecoder decoder) - where TPixel : struct, IPixel - { - return Load(Configuration.Default, path, decoder); - } + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, path, decoder); /// /// Create a new instance of the class from the given file. /// - /// The Configuration. + /// The Configuration. /// The file path to the image. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, IImageDecoder decoder) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index c4336c9aca..332ca471e9 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -16,64 +16,99 @@ namespace SixLabors.ImageSharp public abstract partial class Image { /// - /// By reading the header on the provided stream this calculates the images mime type. + /// By reading the header on the provided stream this calculates the images format type. /// /// The image stream to read the header from. - /// Thrown if the stream is not readable. - /// The mime type or null if none found. - public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream); + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static IImageFormat DetectFormat(Stream stream) + => DetectFormat(Configuration.Default, stream); /// - /// By reading the header on the provided stream this calculates the images mime type. + /// By reading the header on the provided stream this calculates the images format type. /// - /// The configuration. + /// The configuration. /// The image stream to read the header from. - /// Thrown if the stream is not readable. - /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, Stream stream) - => WithSeekableStream(config, stream, s => InternalDetectFormat(s, config)); + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static IImageFormat DetectFormat(Configuration configuration, Stream stream) + => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration)); /// - /// By reading the header on the provided stream this reads the raw image information. + /// Reads the raw image information from the specified stream without fully decoding it. /// /// The image stream to read the header from. - /// Thrown if the stream is not readable. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. /// /// The or null if suitable info detector not found. /// - public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream); + public static IImageInfo Identify(Stream stream) + => Identify(stream, out IImageFormat _); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The image stream to read the header from. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(Stream stream, out IImageFormat format) + => Identify(Configuration.Default, stream, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. /// The image stream to read the information from. - /// Thrown if the stream is not readable. + /// The format type of the decoded image. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. /// /// The or null if suitable info detector is not found. /// - public static IImageInfo Identify(Configuration config, Stream stream) - => WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); + public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format) + { + (IImageInfo info, IImageFormat format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); + + format = data.format; + return data.info; + } /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// /// The stream containing image information. - /// the mime type of the decoded image. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. - /// A new .> - public static Image Load(Stream stream, out IImageFormat format) => Load(Configuration.Default, stream, out format); + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream, out IImageFormat format) + => Load(Configuration.Default, stream, out format); /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// /// The stream containing image information. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. - /// A new .> + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . public static Image Load(Stream stream) => Load(Configuration.Default, stream); /// @@ -82,114 +117,142 @@ namespace SixLabors.ImageSharp /// /// The stream containing image information. /// The decoder. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. - /// A new .> - public static Image Load(Stream stream, IImageDecoder decoder) => Load(Configuration.Default, stream, decoder); + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream, IImageDecoder decoder) + => Load(Configuration.Default, stream, decoder); /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The stream containing image information. /// The decoder. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The configuration is null. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// A new .> - public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) => - WithSeekableStream(config, stream, s => decoder.Decode(config, s)); + public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) + { + Guard.NotNull(decoder, nameof(decoder)); + return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + } /// /// Decode a new instance of the class from the given stream. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The stream containing image information. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// A new .> - public static Image Load(Configuration config, Stream stream) => Load(config, stream, out _); + public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new .> public static Image Load(Stream stream) - where TPixel : struct, IPixel - => Load(null, stream); + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, stream); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// the mime type of the decoded image. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new .> public static Image Load(Stream stream, out IImageFormat format) - where TPixel : struct, IPixel - => Load(null, stream, out format); + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, stream, out format); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. /// The decoder. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new .> public static Image Load(Stream stream, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); /// /// Create a new instance of the class from the given stream. /// - /// The Configuration. + /// The Configuration. /// The stream containing image information. /// The decoder. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) - where TPixel : struct, IPixel - => WithSeekableStream(config, stream, s => decoder.Decode(config, s)); + public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream) - where TPixel : struct, IPixel - => Load(config, stream, out IImageFormat _); + public static Image Load(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => Load(configuration, stream, out IImageFormat _); /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. - /// the mime type of the decoded image. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The format type of the decoded image. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// The pixel format. - /// A new .> - public static Image Load(Configuration config, Stream stream, out IImageFormat format) - where TPixel : struct, IPixel + /// A new . + public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) + where TPixel : unmanaged, IPixel { - config = config ?? Configuration.Default; - (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); format = data.format; @@ -201,9 +264,9 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) { - sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } throw new UnknownImageFormatException(sb.ToString()); @@ -213,16 +276,18 @@ namespace SixLabors.ImageSharp /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. - /// the mime type of the decoded image. - /// Thrown if the stream is not readable. - /// Image cannot be loaded. + /// The format type of the decoded image. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. /// A new . - public static Image Load(Configuration config, Stream stream, out IImageFormat format) + public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) { - config = config ?? Configuration.Default; - (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); format = data.format; @@ -234,16 +299,19 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) { - sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } throw new UnknownImageFormatException(sb.ToString()); } - private static T WithSeekableStream(Configuration config, Stream stream, Func action) + private static T WithSeekableStream(Configuration configuration, Stream stream, Func action) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(stream, nameof(stream)); + if (!stream.CanRead) { throw new NotSupportedException("Cannot read from the stream."); @@ -251,7 +319,7 @@ namespace SixLabors.ImageSharp if (stream.CanSeek) { - if (config.ReadOrigin == ReadOrigin.Begin) + if (configuration.ReadOrigin == ReadOrigin.Begin) { stream.Position = 0; } diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index eb7ce261f8..f36243cc3e 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -20,9 +20,10 @@ namespace SixLabors.ImageSharp /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The data length is incorrect. /// A new . public static Image LoadPixelData(TPixel[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -32,9 +33,10 @@ namespace SixLabors.ImageSharp /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The data length is incorrect. /// A new . public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -44,9 +46,10 @@ namespace SixLabors.ImageSharp /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The data length is incorrect. /// A new . public static Image LoadPixelData(byte[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -56,72 +59,81 @@ namespace SixLabors.ImageSharp /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The data length is incorrect. /// A new . public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// /// Create a new instance of the class from the given byte array in format. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The configuration is null. + /// The data length is incorrect. /// A new . - public static Image LoadPixelData(Configuration config, byte[] data, int width, int height) - where TPixel : struct, IPixel - => LoadPixelData(config, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height); + public static Image LoadPixelData(Configuration configuration, byte[] data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height); /// /// Create a new instance of the class from the given byte array in format. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The configuration is null. + /// The data length is incorrect. /// A new . - public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel - => LoadPixelData(config, MemoryMarshal.Cast(data), width, height); + public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); /// /// Create a new instance of the class from the raw data. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The Span containing the image Pixel data. /// The width of the final image. /// The height of the final image. /// The pixel format. + /// The configuration is null. + /// The data length is incorrect. /// A new . - public static Image LoadPixelData(Configuration config, TPixel[] data, int width, int height) - where TPixel : struct, IPixel - { - return LoadPixelData(config, new ReadOnlySpan(data), width, height); - } + public static Image LoadPixelData(Configuration configuration, TPixel[] data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, new ReadOnlySpan(data), width, height); /// /// Create a new instance of the class from the raw data. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The Span containing the image Pixel data. /// The width of the final image. /// The height of the final image. + /// The configuration is null. + /// The data length is incorrect. /// The pixel format. /// A new . - public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel { + Guard.NotNull(configuration, nameof(configuration)); + int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - var image = new Image(config, width, height); - - data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan()); + var image = new Image(configuration, width, height); + data = data.Slice(0, count); + data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 095991b076..0dd8c814d0 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -20,22 +20,27 @@ namespace SixLabors.ImageSharp /// allowing to view/manipulate it as an ImageSharp instance. /// /// The pixel type - /// The + /// The /// The pixel memory. /// The width of the memory image. /// The height of the memory image. /// The . + /// The configuration is null. + /// The metadata is null. /// An instance public static Image WrapMemory( - Configuration config, + Configuration configuration, Memory pixelMemory, int width, int height, ImageMetadata metadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var memorySource = new MemorySource(pixelMemory); - return new Image(config, memorySource, width, height, metadata); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + var memorySource = MemoryGroup.Wrap(pixelMemory); + return new Image(configuration, memorySource, width, height, metadata); } /// @@ -43,20 +48,19 @@ namespace SixLabors.ImageSharp /// allowing to view/manipulate it as an ImageSharp instance. /// /// The pixel type - /// The + /// The /// The pixel memory. /// The width of the memory image. /// The height of the memory image. + /// The configuration is null. /// An instance. public static Image WrapMemory( - Configuration config, + Configuration configuration, Memory pixelMemory, int width, int height) - where TPixel : struct, IPixel - { - return WrapMemory(config, pixelMemory, width, height, new ImageMetadata()); - } + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata()); /// /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, @@ -72,10 +76,8 @@ namespace SixLabors.ImageSharp Memory pixelMemory, int width, int height) - where TPixel : struct, IPixel - { - return WrapMemory(Configuration.Default, pixelMemory, width, height); - } + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemory, width, height); /// /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, @@ -85,22 +87,27 @@ namespace SixLabors.ImageSharp /// It will be disposed together with the result image. /// /// The pixel type - /// The + /// The /// The that is being transferred to the image /// The width of the memory image. /// The height of the memory image. /// The + /// The configuration is null. + /// The metadata is null. /// An instance public static Image WrapMemory( - Configuration config, + Configuration configuration, IMemoryOwner pixelMemoryOwner, int width, int height, ImageMetadata metadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var memorySource = new MemorySource(pixelMemoryOwner, false); - return new Image(config, memorySource, width, height, metadata); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + var memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, metadata); } /// @@ -111,20 +118,19 @@ namespace SixLabors.ImageSharp /// It will be disposed together with the result image. /// /// The pixel type. - /// The + /// The /// The that is being transferred to the image. /// The width of the memory image. /// The height of the memory image. + /// The configuration is null. /// An instance public static Image WrapMemory( - Configuration config, + Configuration configuration, IMemoryOwner pixelMemoryOwner, int width, int height) - where TPixel : struct, IPixel - { - return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata()); - } + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata()); /// /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, @@ -142,9 +148,7 @@ namespace SixLabors.ImageSharp IMemoryOwner pixelMemoryOwner, int width, int height) - where TPixel : struct, IPixel - { - return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); - } + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index d7fed90164..b1cefdf1d9 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -8,7 +8,6 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -17,20 +16,23 @@ namespace SixLabors.ImageSharp /// For the non-generic type, the pixel type is only known at runtime. /// is always implemented by a pixel-specific instance. /// - public abstract partial class Image : IImage, IConfigurable + public abstract partial class Image : IImage, IConfigurationProvider { private Size size; + private readonly Configuration configuration; /// /// Initializes a new instance of the class. /// - /// The . + /// + /// The configuration which allows altering default behaviour or extending the library. + /// /// The . /// The . /// The . protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size) { - this.Configuration = configuration ?? Configuration.Default; + this.configuration = configuration ?? Configuration.Default; this.PixelType = pixelType; this.size = size; this.Metadata = metadata ?? new ImageMetadata(); @@ -49,11 +51,6 @@ namespace SixLabors.ImageSharp { } - /// - /// Gets the . - /// - protected Configuration Configuration { get; } - /// /// Gets the implementing the public property. /// @@ -76,10 +73,8 @@ namespace SixLabors.ImageSharp /// public ImageFrameCollection Frames => this.NonGenericFrameCollection; - /// - /// Gets the pixel buffer. - /// - Configuration IConfigurable.Configuration => this.Configuration; + /// + Configuration IConfigurationProvider.Configuration => this.configuration; /// public void Dispose() @@ -109,7 +104,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The public Image CloneAs() - where TPixel2 : struct, IPixel => this.CloneAs(this.Configuration); + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); /// /// Returns a copy of the image in the given pixel format. @@ -118,7 +113,7 @@ namespace SixLabors.ImageSharp /// The configuration providing initialization code which allows extending the library. /// The . public abstract Image CloneAs(Configuration configuration) - where TPixel2 : struct, IPixel; + where TPixel2 : unmanaged, IPixel; /// /// Update the size of the image after mutation. @@ -158,7 +153,7 @@ namespace SixLabors.ImageSharp } public void Visit(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.encoder.Encode(image, this.stream); } diff --git a/src/ImageSharp/ImageExtensions.Internal.cs b/src/ImageSharp/ImageExtensions.Internal.cs index 5b5e566659..a1fc51043e 100644 --- a/src/ImageSharp/ImageExtensions.Internal.cs +++ b/src/ImageSharp/ImageExtensions.Internal.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The /// internal static Buffer2D GetRootFramePixelBuffer(this Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.Frames.RootFrame.PixelBuffer; } diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index 6cdc948d40..e5b2a32a99 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -7,12 +7,11 @@ using System.IO; using System.Text; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Extension methods over Image{TPixel}. + /// Extension methods for the type. /// public static partial class ImageExtensions { @@ -20,13 +19,13 @@ namespace SixLabors.ImageSharp /// Writes the image to the given stream using the currently loaded image format. /// /// The source image. - /// The file path to save the image to. - /// Thrown if the stream is null. - public static void Save(this Image source, string filePath) + /// The file path to save the image to. + /// The path is null. + public static void Save(this Image source, string path) { - Guard.NotNullOrWhiteSpace(filePath, nameof(filePath)); + Guard.NotNull(path, nameof(path)); - string ext = Path.GetExtension(filePath); + string ext = Path.GetExtension(path); IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); if (format is null) { @@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:"); foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) { - sb.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}"); + sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); } throw new NotSupportedException(sb.ToString()); @@ -48,26 +47,28 @@ namespace SixLabors.ImageSharp sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { - sb.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); + sb.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); } throw new NotSupportedException(sb.ToString()); } - source.Save(filePath, encoder); + source.Save(path, encoder); } /// /// Writes the image to the given stream using the currently loaded image format. /// /// The source image. - /// The file path to save the image to. + /// The file path to save the image to. /// The encoder to save the image with. - /// Thrown if the encoder is null. - public static void Save(this Image source, string filePath, IImageEncoder encoder) + /// The path is null. + /// The encoder is null. + public static void Save(this Image source, string path, IImageEncoder encoder) { + Guard.NotNull(path, nameof(path)); Guard.NotNull(encoder, nameof(encoder)); - using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath)) + using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) { source.Save(fs, encoder); } @@ -79,10 +80,20 @@ namespace SixLabors.ImageSharp /// The source image. /// The stream to save the image to. /// The format to save the image in. - /// Thrown if the stream is null. + /// The stream is null. + /// The format is null. + /// The stream is not writable. + /// No encoder available for provided format. public static void Save(this Image source, Stream stream, IImageFormat format) { + Guard.NotNull(stream, nameof(stream)); Guard.NotNull(format, nameof(format)); + + if (!stream.CanWrite) + { + throw new NotSupportedException("Cannot write to the stream."); + } + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); if (encoder is null) @@ -92,7 +103,7 @@ namespace SixLabors.ImageSharp foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { - sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } throw new NotSupportedException(sb.ToString()); @@ -103,21 +114,28 @@ namespace SixLabors.ImageSharp /// /// Returns a Base64 encoded string from the given image. + /// The result is prepended with a Data URI + /// + /// + /// For example: + /// + /// + /// /// - /// - /// The pixel format. /// The source image /// The format. + /// The format is null. /// The - public static string ToBase64String(this Image source, IImageFormat format) - where TPixel : struct, IPixel + public static string ToBase64String(this Image source, IImageFormat format) { - using (var stream = new MemoryStream()) - { - source.Save(stream, format); - stream.Flush(); - return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; - } + Guard.NotNull(format, nameof(format)); + + using var stream = new MemoryStream(); + source.Save(stream, format); + + // Always available. + stream.TryGetBuffer(out ArraySegment buffer); + return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array, 0, (int)stream.Length)}"; } } } diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 9e90aeaf59..e6035a177f 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); /// @@ -36,14 +37,15 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); var image = new ImageFrame(configuration, width, height); - data.Slice(0, count).CopyTo(image.GetPixelSpan()); + data = data.Slice(0, count); + data.CopyTo(image.PixelBuffer.FastMemoryGroup); return image; } diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index 91872b21d6..93fa205876 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -2,11 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; - +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -15,37 +14,28 @@ namespace SixLabors.ImageSharp /// In case of animated formats like gif, it contains the single frame in a animation. /// In all other cases it is the only frame of the image. /// - public abstract partial class ImageFrame : IDisposable + public abstract partial class ImageFrame : IConfigurationProvider, IDisposable { + private readonly Configuration configuration; + /// /// Initializes a new instance of the class. /// - /// The . - /// The width. - /// The height. + /// The configuration which allows altering default behaviour or extending the library. + /// The frame width. + /// The frame height. /// The . protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(metadata, nameof(metadata)); - this.Configuration = configuration; - this.MemoryAllocator = configuration.MemoryAllocator; + this.configuration = configuration ?? Configuration.Default; this.Width = width; this.Height = height; this.Metadata = metadata; } - /// - /// Gets the to use for buffer allocations. - /// - public MemoryAllocator MemoryAllocator { get; } - - /// - /// Gets the instance associated with this . - /// - internal Configuration Configuration { get; } - /// /// Gets the width. /// @@ -61,6 +51,9 @@ namespace SixLabors.ImageSharp /// public ImageFrameMetadata Metadata { get; } + /// + Configuration IConfigurationProvider.Configuration => this.configuration; + /// /// Gets the size of the frame. /// @@ -86,8 +79,8 @@ namespace SixLabors.ImageSharp /// Whether to dispose of managed and unmanaged objects. protected abstract void Dispose(bool disposing); - internal abstract void CopyPixelsTo(Span destination) - where TDestinationPixel : struct, IPixel; + internal abstract void CopyPixelsTo(MemoryGroup destination) + where TDestinationPixel : unmanaged, IPixel; /// /// Updates the size of the image frame. diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index c5bd02c79d..c584d2d193 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp public IEnumerator GetEnumerator() => this.NonGenericGetEnumerator(); /// - IEnumerator IEnumerable.GetEnumerator() => this.NonGenericGetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); /// /// Implements . diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 893a7a4a80..89a1dfcc16 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp /// /// The type of the pixel. public sealed class ImageFrameCollection : ImageFrameCollection, IEnumerable> - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly IList> frames = new List>(); private readonly Image parent; @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } - internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) + internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) { this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp this.parent.GetConfiguration(), source.Size(), source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.Span); + source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); return result; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 0436eb9d2b..0171f3d07f 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -2,16 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -22,7 +19,7 @@ namespace SixLabors.ImageSharp /// /// The pixel format. public sealed class ImageFrame : ImageFrame, IPixelSource - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private bool isDisposed; @@ -61,7 +58,7 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); } /// @@ -90,7 +87,7 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height); this.Clear(backgroundColor); } @@ -101,7 +98,7 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } @@ -114,7 +111,7 @@ namespace SixLabors.ImageSharp /// The height of the image in pixels. /// The memory source. /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -134,8 +131,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); - this.PixelBuffer = this.MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); - source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } /// @@ -152,13 +149,56 @@ namespace SixLabors.ImageSharp /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.PixelBuffer[x, y]; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelBuffer.GetElementUnsafe(x, y); + } + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelBuffer.GetElementUnsafe(x, y) = value; + } + } + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The row. + /// The + /// Thrown when row index is out of range. + public Span GetPixelRowSpan(int rowIndex) + { + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.PixelBuffer[x, y] = value; + return this.PixelBuffer.GetRowSpan(rowIndex); + } + + /// + /// Gets the representation of the pixels as a in the source image's pixel format + /// stored in row major order, if the backing buffer is contiguous. + /// + /// The . + /// The . + public bool TryGetSinglePixelSpan(out Span span) + { + IMemoryGroup mg = this.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + span = default; + return false; + } + + span = mg.Single().Span; + return true; } /// @@ -181,7 +221,7 @@ namespace SixLabors.ImageSharp throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.GetPixelSpan().CopyTo(target.GetSpan()); + this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); } /// @@ -213,15 +253,22 @@ namespace SixLabors.ImageSharp this.isDisposed = true; } - internal override void CopyPixelsTo(Span destination) + internal override void CopyPixelsTo(MemoryGroup destination) { if (typeof(TPixel) == typeof(TDestinationPixel)) { - Span dest1 = MemoryMarshal.Cast(destination); - this.PixelBuffer.Span.CopyTo(dest1); + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + { + Span d1 = MemoryMarshal.Cast(d); + s.CopyTo(d1); + }); + return; } - PixelOperations.Instance.To(this.Configuration, this.PixelBuffer.Span, destination); + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + { + PixelOperations.Instance.To(this.GetConfiguration(), s, d); + }); } /// @@ -231,7 +278,7 @@ namespace SixLabors.ImageSharp /// Clones the current instance. /// /// The - internal ImageFrame Clone() => this.Clone(this.Configuration); + internal ImageFrame Clone() => this.Clone(this.GetConfiguration()); /// /// Clones the current instance. @@ -246,7 +293,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The internal ImageFrame CloneAs() - where TPixel2 : struct, IPixel => this.CloneAs(this.Configuration); + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); /// /// Returns a copy of the image frame in the given pixel format. @@ -255,7 +302,7 @@ namespace SixLabors.ImageSharp /// The configuration providing initialization code which allows extending the library. /// The internal ImageFrame CloneAs(Configuration configuration) - where TPixel2 : struct, IPixel + where TPixel2 : unmanaged, IPixel { if (typeof(TPixel2) == typeof(TPixel)) { @@ -263,19 +310,12 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this, target, configuration); - ParallelHelper.IterateRows( - this.Bounds(), + ParallelRowIterator.IterateRowIntervals( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.GetPixelRowSpan(y); - Span targetRow = target.GetPixelRowSpan(y); - PixelOperations.Instance.To(configuration, sourceRow, targetRow); - } - }); + this.Bounds(), + in operation); return target; } @@ -286,15 +326,69 @@ namespace SixLabors.ImageSharp /// The value to initialize the bitmap with. internal void Clear(TPixel value) { - Span span = this.GetPixelSpan(); + MemoryGroup group = this.PixelBuffer.FastMemoryGroup; if (value.Equals(default)) { - span.Clear(); + group.Clear(); } else { - span.Fill(value); + group.Fill(value); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TPixel2 : unmanaged, IPixel + { + private readonly ImageFrame source; + private readonly ImageFrame target; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ImageFrame source, + ImageFrame target, + Configuration configuration) + { + this.source = source; + this.target = target; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.target.GetPixelRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + } } } } diff --git a/src/ImageSharp/ImageInfoExtensions.cs b/src/ImageSharp/ImageInfoExtensions.cs index dca5502d0f..abaa7c4bc4 100644 --- a/src/ImageSharp/ImageInfoExtensions.cs +++ b/src/ImageSharp/ImageInfoExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp { /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 86b0848663..baf4a2ce19 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -1,4 +1,4 @@ - + @@ -10,7 +10,7 @@ $(packageversion) 0.0.1 - netcoreapp2.1;netstandard1.3;netstandard2.0;net472 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 true true @@ -19,28 +19,24 @@ SixLabors.ImageSharp - - - $(DefineConstants);SUPPORTS_MATHF - - - - $(DefineConstants);SUPPORTS_HASHCODE - - - - $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS - + + + - - + + + + - - + + + + + - + True True @@ -76,15 +72,30 @@ True Bgra32.PixelOperations.Generated.tt - + + True + True + Bgra5551.PixelOperations.Generated.tt + + True True - Gray8.PixelOperations.Generated.tt + L8.PixelOperations.Generated.tt - + True True - Gray16.PixelOperations.Generated.tt + L16.PixelOperations.Generated.tt + + + True + True + La16.PixelOperations.Generated.tt + + + True + True + La32.PixelOperations.Generated.tt True @@ -118,10 +129,6 @@ - - - - TextTemplatingFileGenerator @@ -151,13 +158,25 @@ TextTemplatingFileGenerator Bgra32.PixelOperations.Generated.cs - + + TextTemplatingFileGenerator + Bgra5551.PixelOperations.Generated.cs + + TextTemplatingFileGenerator - Gray8.PixelOperations.Generated.cs + L8.PixelOperations.Generated.cs - + TextTemplatingFileGenerator - Gray16.PixelOperations.Generated.cs + L16.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + La16.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + La32.PixelOperations.Generated.cs TextTemplatingFileGenerator @@ -185,21 +204,9 @@ - - - - - - - - - - - - - + diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings index 018ca75cdf..6896e069c2 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -2,6 +2,8 @@ True True True + True + True True True True diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 3f733479dc..f6173db972 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp /// /// The pixel format. public sealed class Image : Image - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private bool isDisposed; @@ -75,22 +75,22 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class - /// wrapping an external . + /// wrapping an external . /// /// The configuration providing initialization code which allows extending the library. - /// The memory source. + /// The memory source. /// The width of the image in pixels. /// The height of the image in pixels. /// The images metadata. internal Image( Configuration configuration, - MemorySource memorySource, + MemoryGroup memoryGroup, int width, int height, ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memoryGroup); } /// @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp protected override ImageFrameCollection NonGenericFrameCollection => this.Frames; /// - /// Gets the frames. + /// Gets the collection of image frames. /// public new ImageFrameCollection Frames { get; } @@ -145,17 +145,63 @@ namespace SixLabors.ImageSharp /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { - get => this.PixelSource.PixelBuffer[x, y]; - set => this.PixelSource.PixelBuffer[x, y] = value; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y); + } + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value; + } + } + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The row. + /// The + /// Thrown when row index is out of range. + public Span GetPixelRowSpan(int rowIndex) + { + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); + + return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex); + } + + /// + /// Gets the representation of the pixels as a in the source image's pixel format + /// stored in row major order, if the backing buffer is contiguous. + /// + /// The . + /// The . + public bool TryGetSinglePixelSpan(out Span span) + { + IMemoryGroup mg = this.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + span = default; + return false; + } + + span = mg.Single().Span; + return true; } /// /// Clones the current image /// /// Returns a new image with all the same metadata as the original. - public Image Clone() => this.Clone(this.Configuration); + public Image Clone() => this.Clone(this.GetConfiguration()); /// /// Clones the current image with the given configuration. @@ -166,8 +212,12 @@ namespace SixLabors.ImageSharp { this.EnsureNotDisposed(); - IEnumerable> clonedFrames = - this.Frames.Select, ImageFrame>(x => x.Clone(configuration)); + var clonedFrames = new ImageFrame[this.Frames.Count]; + for (int i = 0; i < clonedFrames.Length; i++) + { + clonedFrames[i] = this.Frames[i].Clone(configuration); + } + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } @@ -181,8 +231,12 @@ namespace SixLabors.ImageSharp { this.EnsureNotDisposed(); - IEnumerable> clonedFrames = - this.Frames.Select, ImageFrame>(x => x.CloneAs(configuration)); + var clonedFrames = new ImageFrame[this.Frames.Count]; + for (int i = 0; i < clonedFrames.Length; i++) + { + clonedFrames[i] = this.Frames[i].CloneAs(configuration); + } + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } @@ -258,5 +312,25 @@ namespace SixLabors.ImageSharp return rootSize; } + + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } } } diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs new file mode 100644 index 0000000000..07451029e6 --- /dev/null +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp +{ + /// + /// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. + /// + /// The pixel format. + public sealed class IndexedImageFrame : IPixelSource, IDisposable + where TPixel : unmanaged, IPixel + { + private Buffer2D pixelBuffer; + private IMemoryOwner paletteOwner; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The frame width. + /// The frame height. + /// The color palette. + internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeLessThanOrEqualTo(palette.Length, QuantizerConstants.MaxColors, nameof(palette)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Configuration = configuration; + this.Width = width; + this.Height = height; + this.pixelBuffer = configuration.MemoryAllocator.Allocate2D(width, height); + + // Copy the palette over. We want the lifetime of this frame to be independant of any palette source. + this.paletteOwner = configuration.MemoryAllocator.Allocate(palette.Length); + palette.Span.CopyTo(this.paletteOwner.GetSpan()); + this.Palette = this.paletteOwner.Memory.Slice(0, palette.Length); + } + + /// + /// Gets the configuration which allows altering default behaviour or extending the library. + /// + public Configuration Configuration { get; } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public ReadOnlyMemory Palette { get; } + + /// + Buffer2D IPixelSource.PixelBuffer => this.pixelBuffer; + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelRowSpan(int rowIndex) + => this.GetWritablePixelRowSpanUnsafe(rowIndex); + + /// + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// + /// Note: Values written to this span are not sanitized against the palette length. + /// Care should be taken during assignment to prevent out-of-bounds errors. + /// + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetWritablePixelRowSpanUnsafe(int rowIndex) + => this.pixelBuffer.GetRowSpan(rowIndex); + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.pixelBuffer.Dispose(); + this.paletteOwner.Dispose(); + this.pixelBuffer = null; + this.paletteOwner = null; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs new file mode 100644 index 0000000000..4edb702ed9 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Options for allocating buffers. + /// + public enum AllocationOptions + { + /// + /// Indicates that the buffer should just be allocated. + /// + None, + + /// + /// Indicates that the allocated buffer should be cleaned following allocation. + /// + Clean + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs new file mode 100644 index 0000000000..f943598301 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains and . + /// + public partial class ArrayPoolMemoryAllocator + { + /// + /// The buffer implementation of . + /// + private class Buffer : ManagedBufferBase + where T : struct + { + /// + /// The length of the buffer. + /// + private readonly int length; + + /// + /// A weak reference to the source pool. + /// + /// + /// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed + /// after a call to , regardless of having buffer instances still being in use. + /// + private WeakReference> sourcePoolReference; + + public Buffer(byte[] data, int length, ArrayPool sourcePool) + { + this.Data = data; + this.length = length; + this.sourcePoolReference = new WeakReference>(sourcePool); + } + + /// + /// Gets the buffer as a byte array. + /// + protected byte[] Data { get; private set; } + + /// + public override Span GetSpan() + { + if (this.Data is null) + { + ThrowObjectDisposedException(); + } + + return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); + } + + /// + protected override void Dispose(bool disposing) + { + if (!disposing || this.Data is null || this.sourcePoolReference is null) + { + return; + } + + if (this.sourcePoolReference.TryGetTarget(out ArrayPool pool)) + { + pool.Return(this.Data); + } + + this.sourcePoolReference = null; + this.Data = null; + } + + protected override object GetPinnableObject() => this.Data; + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); + } + } + + /// + /// The implementation of . + /// + private sealed class ManagedByteBuffer : Buffer, IManagedByteBuffer + { + public ManagedByteBuffer(byte[] data, int length, ArrayPool sourcePool) + : base(data, length, sourcePool) + { + } + + /// + public byte[] Array => this.Data; + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs new file mode 100644 index 0000000000..5ef60c9ed6 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains common factory methods and configuration constants. + /// + public partial class ArrayPoolMemoryAllocator + { + /// + /// The default value for: maximum size of pooled arrays in bytes. + /// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data. + /// + internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024; + + /// + /// The value for: The threshold to pool arrays in which has less buckets for memory safety. + /// + private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024; + + /// + /// The default bucket count for . + /// + private const int DefaultLargePoolBucketCount = 6; + + /// + /// The default bucket count for . + /// + private const int DefaultNormalPoolBucketCount = 16; + + // TODO: This value should be determined by benchmarking + private const int DefaultBufferCapacityInBytes = int.MaxValue / 4; + + /// + /// This is the default. Should be good for most use cases. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateDefault() + { + return new ArrayPoolMemoryAllocator( + DefaultMaxPooledBufferSizeInBytes, + DefaultBufferSelectorThresholdInBytes, + DefaultLargePoolBucketCount, + DefaultNormalPoolBucketCount, + DefaultBufferCapacityInBytes); + } + + /// + /// For environments with very limited memory capabilities, only small buffers like image rows are pooled. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateWithMinimalPooling() + { + return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24); + } + + /// + /// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateWithModeratePooling() + { + return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24); + } + + /// + /// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput. + /// + /// The memory manager. + public static ArrayPoolMemoryAllocator CreateWithAggressivePooling() + { + return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs new file mode 100644 index 0000000000..4a04cb5d6e --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -0,0 +1,189 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by allocating memory from . + /// + public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator + { + private readonly int maxArraysPerBucketNormalPool; + + private readonly int maxArraysPerBucketLargePool; + + /// + /// The for small-to-medium buffers which is not kept clean. + /// + private ArrayPool normalArrayPool; + + /// + /// The for huge buffers, which is not kept clean. + /// + private ArrayPool largeArrayPool; + + /// + /// Initializes a new instance of the class. + /// + public ArrayPoolMemoryAllocator() + : this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes) + : this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// Arrays over this threshold will be pooled in which has less buckets for memory safety. + public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) + : this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// The threshold to pool arrays in which has less buckets for memory safety. + /// Max arrays per bucket for the large array pool. + /// Max arrays per bucket for the normal array pool. + public ArrayPoolMemoryAllocator( + int maxPoolSizeInBytes, + int poolSelectorThresholdInBytes, + int maxArraysPerBucketLargePool, + int maxArraysPerBucketNormalPool) + : this( + maxPoolSizeInBytes, + poolSelectorThresholdInBytes, + maxArraysPerBucketLargePool, + maxArraysPerBucketNormalPool, + DefaultBufferCapacityInBytes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// The threshold to pool arrays in which has less buckets for memory safety. + /// Max arrays per bucket for the large array pool. + /// Max arrays per bucket for the normal array pool. + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + public ArrayPoolMemoryAllocator( + int maxPoolSizeInBytes, + int poolSelectorThresholdInBytes, + int maxArraysPerBucketLargePool, + int maxArraysPerBucketNormalPool, + int bufferCapacityInBytes) + { + Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); + Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); + + this.MaxPoolSizeInBytes = maxPoolSizeInBytes; + this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; + this.BufferCapacityInBytes = bufferCapacityInBytes; + this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; + this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; + + this.InitArrayPools(); + } + + /// + /// Gets the maximum size of pooled arrays in bytes. + /// + public int MaxPoolSizeInBytes { get; } + + /// + /// Gets the threshold to pool arrays in which has less buckets for memory safety. + /// + public int PoolSelectorThresholdInBytes { get; } + + /// + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests + + /// + public override void ReleaseRetainedResources() + { + this.InitArrayPools(); + } + + /// + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; + + /// + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + int itemSizeBytes = Unsafe.SizeOf(); + int bufferSizeInBytes = length * itemSizeBytes; + if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes) + { + ThrowInvalidAllocationException(length); + } + + ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); + byte[] byteArray = pool.Rent(bufferSizeInBytes); + + var buffer = new Buffer(byteArray, length, pool); + if (options == AllocationOptions.Clean) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + /// + public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + + ArrayPool pool = this.GetArrayPool(length); + byte[] byteArray = pool.Rent(length); + + var buffer = new ManagedByteBuffer(byteArray, length, pool); + if (options == AllocationOptions.Clean) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) + { + return maxPoolSizeInBytes / 4; + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowInvalidAllocationException(int length) => + throw new InvalidMemoryOperationException( + $"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); + + private ArrayPool GetArrayPool(int bufferSizeInBytes) + { + return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; + } + + private void InitArrayPools() + { + this.largeArrayPool = ArrayPool.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool); + this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs b/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs new file mode 100644 index 0000000000..cb1f58ddba --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. + /// + public interface IManagedByteBuffer : IMemoryOwner + { + /// + /// Gets the managed array backing this buffer instance. + /// + byte[] Array { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs new file mode 100644 index 0000000000..56057f3726 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Wraps an array as an instance. + /// + /// + internal class BasicArrayBuffer : ManagedBufferBase + where T : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The array. + /// The length of the buffer. + public BasicArrayBuffer(T[] array, int length) + { + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); + this.Array = array; + this.Length = length; + } + + /// + /// Initializes a new instance of the class. + /// + /// The array. + public BasicArrayBuffer(T[] array) + : this(array, array.Length) + { + } + + /// + /// Gets the array. + /// + public T[] Array { get; } + + /// + /// Gets the length. + /// + public int Length { get; } + + /// + public override Span GetSpan() => this.Array.AsSpan(0, this.Length); + + /// + protected override void Dispose(bool disposing) + { + } + + /// + protected override object GetPinnableObject() + { + return this.Array; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs new file mode 100644 index 0000000000..571ad70c5d --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Provides an based on . + /// + internal sealed class BasicByteBuffer : BasicArrayBuffer, IManagedByteBuffer + { + /// + /// Initializes a new instance of the class. + /// + /// The byte array. + internal BasicByteBuffer(byte[] array) + : base(array) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs new file mode 100644 index 0000000000..8909638600 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Provides a base class for implementations by implementing pinning logic for adaption. + /// + /// The element type. + internal abstract class ManagedBufferBase : MemoryManager + where T : struct + { + private GCHandle pinHandle; + + /// + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + if (!this.pinHandle.IsAllocated) + { + this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); + } + + void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); + return new MemoryHandle(ptr, this.pinHandle); + } + + /// + public override void Unpin() + { + if (this.pinHandle.IsAllocated) + { + this.pinHandle.Free(); + } + } + + /// + /// Gets the object that should be pinned. + /// + /// The pinnable . + protected abstract object GetPinnableObject(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs new file mode 100644 index 0000000000..a4e1de1977 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Memory managers are used to allocate memory for image processing operations. + /// + public abstract class MemoryAllocator + { + /// + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. + /// + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + protected internal abstract int GetBufferCapacityInBytes(); + + /// + /// Allocates an , holding a of length . + /// + /// Type of the data stored in the buffer. + /// Size of the buffer to allocate. + /// The allocation options. + /// A buffer of values of type . + /// When length is zero or negative. + /// When length is over the capacity of the allocator. + public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + where T : struct; + + /// + /// Allocates an . + /// + /// The requested buffer length. + /// The allocation options. + /// The . + /// When length is zero or negative. + /// When length is over the capacity of the allocator. + public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); + + /// + /// Releases all retained resources not being in use. + /// Eg: by resetting array pools and letting GC to free the arrays. + /// + public virtual void ReleaseRetainedResources() + { + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs new file mode 100644 index 0000000000..4c62e4ded5 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by newing up managed arrays on every allocation request. + /// + public sealed class SimpleGcMemoryAllocator : MemoryAllocator + { + /// + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + + /// + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + + return new BasicArrayBuffer(new T[length]); + } + + /// + public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + + return new BasicByteBuffer(new byte[length]); + } + } +} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 59247aa2d2..fea44f52c1 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -3,23 +3,36 @@ using System; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Memory { /// /// Defines extension methods for . /// - internal static class Buffer2DExtensions + public static class Buffer2DExtensions { /// + /// Gets the backing . + /// + /// The buffer. + /// The element type. + /// The MemoryGroup. + public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) + where T : struct + { + Guard.NotNull(buffer, nameof(buffer)); + return buffer.FastMemoryGroup.View; + } + + /// + /// TODO: Does not work with multi-buffer groups, should be specific to Resize. /// Copy columns of inplace, /// from positions starting at to positions at . /// - public static unsafe void CopyColumns( + internal static unsafe void CopyColumns( this Buffer2D buffer, int sourceIndex, int destIndex, @@ -37,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory int dOffset = destIndex * elementSize; long count = columnCount * elementSize; - Span span = MemoryMarshal.AsBytes(buffer.Memory.Span); + Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span); fixed (byte* ptr = span) { @@ -60,7 +73,7 @@ namespace SixLabors.ImageSharp.Memory /// The element type /// The /// The - public static Rectangle FullRectangle(this Buffer2D buffer) + internal static Rectangle FullRectangle(this Buffer2D buffer) where T : struct { return new Rectangle(0, 0, buffer.Width, buffer.Height); @@ -73,11 +86,11 @@ namespace SixLabors.ImageSharp.Memory /// The /// The rectangle subarea /// The - public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) + internal static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) where T : struct => new BufferArea(buffer, rectangle); - public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) + internal static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height)); @@ -87,87 +100,22 @@ namespace SixLabors.ImageSharp.Memory /// The element type /// The /// The - public static BufferArea GetArea(this Buffer2D buffer) + internal static BufferArea GetArea(this Buffer2D buffer) where T : struct => new BufferArea(buffer); - public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY) - where T : struct => - new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY)); - - /// - /// Gets a span for all the pixels in defined by - /// - public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) - where T : struct - { - return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The buffer - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory GetRowMemory(this Buffer2D buffer, int y) - where T : struct - { - return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. - /// - /// The buffer - /// The x coordinate (position in the row) - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetRowSpan(this Buffer2D buffer, int x, int y) - where T : struct - { - return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The buffer - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetRowSpan(this Buffer2D buffer, int y) - where T : struct - { - return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); - } - /// /// Returns the size of the buffer. /// /// The element type /// The /// The of the buffer - public static Size Size(this Buffer2D buffer) + internal static Size Size(this Buffer2D buffer) where T : struct { return new Size(buffer.Width, buffer.Height); } - /// - /// Gets a to the backing buffer of . - /// - internal static Span GetSpan(this Buffer2D buffer) - where T : struct - { - return buffer.MemorySource.GetSpan(); - } - [Conditional("DEBUG")] private static void CheckColumnRegionsDoNotOverlap( Buffer2D buffer, diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 82a98bfc63..ada1d29b6d 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; - -using SixLabors.Primitives; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { @@ -12,23 +12,31 @@ namespace SixLabors.ImageSharp.Memory /// Represents a buffer of value type objects /// interpreted as a 2D region of x elements. /// + /// + /// Before RC1, this class might be target of API changes, use it on your own risk! + /// /// The value type. - internal sealed class Buffer2D : IDisposable + public sealed class Buffer2D : IDisposable where T : struct { - private MemorySource memorySource; + private Memory cachedMemory = default; /// /// Initializes a new instance of the class. /// - /// The buffer to wrap - /// The number of elements in a row - /// The number of rows - public Buffer2D(MemorySource memorySource, int width, int height) + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - this.memorySource = memorySource; + this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; + + if (memoryGroup.Count == 1) + { + this.cachedMemory = memoryGroup[0]; + } } /// @@ -42,13 +50,20 @@ namespace SixLabors.ImageSharp.Memory public int Height { get; private set; } /// - /// Gets the backing + /// Gets the backing . /// - public MemorySource MemorySource => this.memorySource; - - public Memory Memory => this.MemorySource.Memory; + /// The MemoryGroup. + public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; - public Span Span => this.Memory.Span; + /// + /// Gets the backing without the view abstraction. + /// + /// + /// This property has been kept internal intentionally. + /// It's public counterpart is , + /// which only exposes the view of the MemoryGroup. + /// + internal MemoryGroup FastMemoryGroup { get; } /// /// Gets a reference to the element at the specified position. @@ -56,55 +71,151 @@ namespace SixLabors.ImageSharp.Memory /// The x coordinate (row) /// The y coordinate (position at row) /// A reference to the element. + /// When index is out of range of the buffer. public ref T this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get { + DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x)); + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - Span span = this.Span; - return ref span[(this.Width * y) + x]; + return ref this.GetRowSpan(y)[x]; } } /// - /// Creates a new instance that maps to a target rows interval from the current instance. + /// Disposes the instance + /// + public void Dispose() + { + this.FastMemoryGroup.Dispose(); + this.cachedMemory = default; + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// - /// The target vertical offset for the rows interval to retrieve. - /// The desired number of rows to extract. - /// The new instance with the requested rows interval. - public Buffer2D Slice(int y, int h) + /// + /// This method does not validate the y argument for performance reason, + /// is being propagated from lower levels. + /// + /// The row index. + /// The of the pixels in the row. + /// Thrown when row index is out of range. + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetRowSpan(int y) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeGreaterThan(h, 0, nameof(h)); - DebugGuard.MustBeLessThanOrEqualTo(y + h, this.Height, nameof(h)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y).Span; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal ref T GetElementUnsafe(int x, int y) + { + if (this.cachedMemory.Length > 0) + { + Span span = this.cachedMemory.Span; + ref T start = ref MemoryMarshal.GetReference(span); + return ref Unsafe.Add(ref start, (y * this.Width) + x); + } - Memory slice = this.Memory.Slice(y * this.Width, h * this.Width); - var memory = new MemorySource(slice); - return new Buffer2D(memory, this.Width, h); + return ref this.GetElementSlow(x, y); } /// - /// Disposes the instance + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// This method is intended for internal use only, since it does not use the indirection provided by + /// . /// - public void Dispose() + /// The y (row) coordinate. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetFastRowMemory(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// The y (row) coordinate. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetSafeRowMemory(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return this.FastMemoryGroup.View.GetBoundedSlice(y * (long)this.Width, this.Width); + } + + /// + /// Gets a to the backing data if the backing group consists of a single contiguous memory buffer. + /// Throws otherwise. + /// + /// The referencing the memory area. + /// + /// Thrown when the backing group is discontiguous. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal Span GetSingleSpan() + { + // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup + return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.GetSingleSpanSlow(); + } + + /// + /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. + /// Throws otherwise. + /// + /// The . + /// + /// Thrown when the backing group is discontiguous. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetSingleMemory() { - this.MemorySource.Dispose(); + // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup + return this.cachedMemory.Length != 0 ? this.cachedMemory : this.GetSingleMemorySlow(); } /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! /// - public static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) + internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) + { + bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); + SwapOwnData(destination, source, swap); + } + + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); + + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetSingleMemorySlow() => this.FastMemoryGroup.Single(); + + [MethodImpl(InliningOptions.ColdPath)] + private Span GetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; + + [MethodImpl(InliningOptions.ColdPath)] + private ref T GetElementSlow(int x, int y) { - MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); - SwapDimensionData(destination, source); + Span span = this.GetRowMemorySlow(y).Span; + return ref span[x]; } - private static void SwapDimensionData(Buffer2D a, Buffer2D b) + private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -114,6 +225,13 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; + + if (swapCachedMemory) + { + Memory aCached = a.cachedMemory; + a.cachedMemory = b.cachedMemory; + b.cachedMemory = aCached; + } } } } diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index 38f0b8129d..076f7f37ce 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -3,15 +3,13 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Memory { /// /// Represents a rectangular area inside a 2D memory buffer (). /// This type is kind-of 2D Span, but it can live on heap. /// - /// The element type + /// The element type. internal readonly struct BufferArea where T : struct { @@ -74,15 +72,19 @@ namespace SixLabors.ImageSharp.Memory /// The position inside a row /// The row index /// The reference to the value - public ref T this[int x, int y] => ref this.DestinationBuffer.GetSpan()[this.GetIndexOf(x, y)]; + public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y]; /// /// Gets a reference to the [0,0] element. /// /// The reference to the [0,0] element [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T GetReferenceToOrigin() => - ref this.DestinationBuffer.GetSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; + public ref T GetReferenceToOrigin() + { + int y = this.Rectangle.Y; + int x = this.Rectangle.X; + return ref this.DestinationBuffer.GetRowSpan(y)[x]; + } /// /// Gets a span to row 'y' inside this area. @@ -92,20 +94,20 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetRowSpan(int y) { - int yy = this.GetRowIndex(y); + int yy = this.Rectangle.Y + y; int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.DestinationBuffer.GetSpan().Slice(yy + xx, width); + return this.DestinationBuffer.GetRowSpan(yy).Slice(xx, width); } /// /// Returns a sub-area as . (Similar to .) /// - /// The x index at the subarea origo - /// The y index at the subarea origo - /// The desired width of the subarea - /// The desired height of the subarea + /// The x index at the subarea origin. + /// The y index at the subarea origin. + /// The desired width of the subarea. + /// The desired height of the subarea. /// The subarea [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferArea GetSubArea(int x, int y, int width, int height) @@ -131,26 +133,12 @@ namespace SixLabors.ImageSharp.Memory return new BufferArea(this.DestinationBuffer, rectangle); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetIndexOf(int x, int y) - { - int yy = this.GetRowIndex(y); - int xx = this.Rectangle.X + x; - return yy + xx; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal int GetRowIndex(int y) - { - return (y + this.Rectangle.Y) * this.DestinationBuffer.Width; - } - public void Clear() { // Optimization for when the size of the area is the same as the buffer size. if (this.IsFullBufferArea) { - this.DestinationBuffer.GetSpan().Clear(); + this.DestinationBuffer.FastMemoryGroup.Clear(); return; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs new file mode 100644 index 0000000000..3bb6b8d336 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents discontiguous group of multiple uniformly-sized memory segments. + /// The last segment can be smaller than the preceding ones. + /// + /// The element type. + public interface IMemoryGroup : IReadOnlyList> + where T : struct + { + /// + /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. + /// The last buffer is allowed to be smaller. + /// + int BufferLength { get; } + + /// + /// Gets the aggregate number of elements in the group. + /// + long TotalLength { get; } + + /// + /// Gets a value indicating whether the group has been invalidated. + /// + /// + /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces + /// the image buffers internally. + /// + bool IsValid { get; } + + /// + /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current + /// instance. The return type shouldn't be used directly: just use a block on + /// the instance in use and the C# compiler will automatically invoke this + /// method behind the scenes. This method takes precedence over the + /// implementation, which is still available when casting to one of the underlying interfaces. + /// + /// A new instance mapping the current values in use. + new MemoryGroupEnumerator GetEnumerator(); + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs new file mode 100644 index 0000000000..1bc44e33e1 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// A value-type enumerator for instances. + /// + /// The element type. + [EditorBrowsable(EditorBrowsableState.Never)] + public ref struct MemoryGroupEnumerator + where T : struct + { + private readonly IMemoryGroup memoryGroup; + private readonly int count; + private int index; + + [MethodImpl(InliningOptions.ShortMethod)] + internal MemoryGroupEnumerator(MemoryGroup.Owned memoryGroup) + { + this.memoryGroup = memoryGroup; + this.count = memoryGroup.Count; + this.index = -1; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal MemoryGroupEnumerator(MemoryGroup.Consumed memoryGroup) + { + this.memoryGroup = memoryGroup; + this.count = memoryGroup.Count; + this.index = -1; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal MemoryGroupEnumerator(MemoryGroupView memoryGroup) + { + this.memoryGroup = memoryGroup; + this.count = memoryGroup.Count; + this.index = -1; + } + + /// + public Memory Current + { + [MethodImpl(InliningOptions.ShortMethod)] + get => this.memoryGroup[this.index]; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool MoveNext() + { + int index = this.index + 1; + + if (index < this.count) + { + this.index = index; + + return true; + } + + return false; + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs new file mode 100644 index 0000000000..295f9190a9 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -0,0 +1,238 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal static class MemoryGroupExtensions + { + internal static void Fill(this IMemoryGroup group, T value) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Fill(value); + } + } + + internal static void Clear(this IMemoryGroup group) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Clear(); + } + } + + /// + /// Returns a slice that is expected to be within the bounds of a single buffer. + /// Otherwise is thrown. + /// + internal static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length) + where T : struct + { + Guard.NotNull(group, nameof(group)); + Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!"); + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); + + int bufferIdx = (int)(start / group.BufferLength); + + if (bufferIdx < 0) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + if (bufferIdx >= group.Count) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + int bufferStart = (int)(start % group.BufferLength); + int bufferEnd = bufferStart + length; + Memory memory = group[bufferIdx]; + + if (bufferEnd > memory.Length) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return memory.Slice(bufferStart, length); + } + + internal static void CopyTo(this IMemoryGroup source, Span target) + where T : struct + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); + + var cur = new MemoryGroupCursor(source); + long position = 0; + while (position < source.TotalLength) + { + int fwd = Math.Min(cur.LookAhead(), target.Length); + cur.GetSpan(fwd).CopyTo(target); + + cur.Forward(fwd); + target = target.Slice(fwd); + position += fwd; + } + } + + internal static void CopyTo(this Span source, IMemoryGroup target) + where T : struct + => CopyTo((ReadOnlySpan)source, target); + + internal static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) + where T : struct + { + Guard.NotNull(target, nameof(target)); + Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); + + var cur = new MemoryGroupCursor(target); + + while (!source.IsEmpty) + { + int fwd = Math.Min(cur.LookAhead(), source.Length); + source.Slice(0, fwd).CopyTo(cur.GetSpan(fwd)); + cur.Forward(fwd); + source = source.Slice(fwd); + } + } + + internal static void CopyTo(this IMemoryGroup source, IMemoryGroup target) + where T : struct + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + srcSpan.CopyTo(trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + internal static void TransformTo( + this IMemoryGroup source, + IMemoryGroup target, + TransformItemsDelegate transform) + where TSource : struct + where TTarget : struct + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.NotNull(transform, nameof(transform)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + transform(srcSpan, trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + internal static void TransformInplace( + this IMemoryGroup memoryGroup, + TransformItemsInplaceDelegate transform) + where T : struct + { + foreach (Memory memory in memoryGroup) + { + transform(memory.Span); + } + } + + internal static bool IsEmpty(this IMemoryGroup group) + where T : struct + => group.Count == 0; + + private struct MemoryGroupCursor + where T : struct + { + private readonly IMemoryGroup memoryGroup; + + private int bufferIndex; + + private int elementIndex; + + public MemoryGroupCursor(IMemoryGroup memoryGroup) + { + this.memoryGroup = memoryGroup; + this.bufferIndex = 0; + this.elementIndex = 0; + } + + private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; + + private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; + + public Span GetSpan(int length) + { + return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); + } + + public int LookAhead() + { + return this.CurrentBufferLength - this.elementIndex; + } + + public void Forward(int steps) + { + int nextIdx = this.elementIndex + steps; + int currentBufferLength = this.CurrentBufferLength; + + if (nextIdx < currentBufferLength) + { + this.elementIndex = nextIdx; + } + else if (nextIdx == currentBufferLength) + { + this.bufferIndex++; + this.elementIndex = 0; + } + else + { + // If we get here, it indicates a bug in CopyTo: + throw new ArgumentException("Can't forward multiple buffers!", nameof(steps)); + } + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs new file mode 100644 index 0000000000..1698f08d17 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -0,0 +1,145 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements , defining a view for + /// rather than owning the segments. + /// + /// + /// This type provides an indirection, protecting the users of publicly exposed memory API-s + /// from internal memory-swaps. Whenever an internal swap happens, the + /// instance becomes invalid, throwing an exception on all operations. + /// + /// The element type. + internal class MemoryGroupView : IMemoryGroup + where T : struct + { + private MemoryGroup owner; + private readonly MemoryOwnerWrapper[] memoryWrappers; + + public MemoryGroupView(MemoryGroup owner) + { + this.owner = owner; + this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; + + for (int i = 0; i < owner.Count; i++) + { + this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); + } + } + + public int Count + { + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.EnsureIsValid(); + return this.owner.Count; + } + } + + public int BufferLength + { + get + { + this.EnsureIsValid(); + return this.owner.BufferLength; + } + } + + public long TotalLength + { + get + { + this.EnsureIsValid(); + return this.owner.TotalLength; + } + } + + public bool IsValid => this.owner != null; + + public Memory this[int index] + { + get + { + this.EnsureIsValid(); + return this.memoryWrappers[index].Memory; + } + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(this); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + this.EnsureIsValid(); + for (int i = 0; i < this.Count; i++) + { + yield return this.memoryWrappers[i].Memory; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + + internal void Invalidate() + { + this.owner = null; + } + + private void EnsureIsValid() + { + if (!this.IsValid) + { + throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); + } + } + + private class MemoryOwnerWrapper : MemoryManager + { + private readonly MemoryGroupView view; + + private readonly int index; + + public MemoryOwnerWrapper(MemoryGroupView view, int index) + { + this.view = view; + this.index = index; + } + + protected override void Dispose(bool disposing) + { + } + + public override Span GetSpan() + { + this.view.EnsureIsValid(); + return this.view.owner[this.index].Span; + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + this.view.EnsureIsValid(); + return this.view.owner[this.index].Pin(); + } + + public override void Unpin() + { + throw new NotSupportedException(); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs new file mode 100644 index 0000000000..1dfbaea932 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + internal abstract partial class MemoryGroup + { + /// + /// A implementation that consumes the underlying memory buffers. + /// + public sealed class Consumed : MemoryGroup, IEnumerable> + { + private readonly Memory[] source; + + public Consumed(Memory[] source, int bufferLength, long totalLength) + : base(bufferLength, totalLength) + { + this.source = source; + this.View = new MemoryGroupView(this); + } + + public override int Count + { + [MethodImpl(InliningOptions.ShortMethod)] + get => this.source.Length; + } + + public override Memory this[int index] => this.source[index]; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(this); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + /* The runtime sees the Array class as if it implemented the + * type-generic collection interfaces explicitly, so here we + * can just cast the source array to IList> (or to + * an equivalent type), and invoke the generic GetEnumerator + * method directly from that interface reference. This saves + * having to create our own iterator block here. */ + return ((IList>)this.source).GetEnumerator(); + } + + public override void Dispose() + { + this.View.Invalidate(); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs new file mode 100644 index 0000000000..5a86ac4268 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + internal abstract partial class MemoryGroup + { + /// + /// A implementation that owns the underlying memory buffers. + /// + public sealed class Owned : MemoryGroup, IEnumerable> + { + private IMemoryOwner[] memoryOwners; + + public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) + : base(bufferLength, totalLength) + { + this.memoryOwners = memoryOwners; + this.Swappable = swappable; + this.View = new MemoryGroupView(this); + } + + public bool Swappable { get; } + + private bool IsDisposed => this.memoryOwners == null; + + public override int Count + { + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.EnsureNotDisposed(); + return this.memoryOwners.Length; + } + } + + public override Memory this[int index] + { + get + { + this.EnsureNotDisposed(); + return this.memoryOwners[index].Memory; + } + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(this); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + this.EnsureNotDisposed(); + return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); + } + + public override void Dispose() + { + if (this.IsDisposed) + { + return; + } + + this.View.Invalidate(); + + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + memoryOwner.Dispose(); + } + + this.memoryOwners = null; + this.IsValid = false; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void EnsureNotDisposed() + { + if (this.memoryOwners is null) + { + ThrowObjectDisposedException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(nameof(MemoryGroup)); + } + + internal static void SwapContents(Owned a, Owned b) + { + a.EnsureNotDisposed(); + b.EnsureNotDisposed(); + + IMemoryOwner[] tempOwners = a.memoryOwners; + long tempTotalLength = a.TotalLength; + int tempBufferLength = a.BufferLength; + + a.memoryOwners = b.memoryOwners; + a.TotalLength = b.TotalLength; + a.BufferLength = b.BufferLength; + + b.memoryOwners = tempOwners; + b.TotalLength = tempTotalLength; + b.BufferLength = tempBufferLength; + + a.View.Invalidate(); + b.View.Invalidate(); + a.View = new MemoryGroupView(a); + b.View = new MemoryGroupView(b); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs new file mode 100644 index 0000000000..6fd93f12ea --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -0,0 +1,200 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents discontinuous group of multiple uniformly-sized memory segments. + /// The underlying buffers may change with time, therefore it's not safe to expose them directly on + /// and . + /// + /// The element type. + internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable + where T : struct + { + private static readonly int ElementSize = Unsafe.SizeOf(); + + private MemoryGroup(int bufferLength, long totalLength) + { + this.BufferLength = bufferLength; + this.TotalLength = totalLength; + } + + /// + public abstract int Count { get; } + + /// + public int BufferLength { get; private set; } + + /// + public long TotalLength { get; private set; } + + /// + public bool IsValid { get; private set; } = true; + + public MemoryGroupView View { get; private set; } + + /// + public abstract Memory this[int index] { get; } + + /// + public abstract void Dispose(); + + /// + public abstract MemoryGroupEnumerator GetEnumerator(); + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + /* This method is implemented in each derived class. + * Implementing the method here as non-abstract and throwing, + * then reimplementing it explicitly in each derived class, is + * a workaround for the lack of support for abstract explicit + * interface method implementations in C#. */ + throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); + } + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + + /// + /// Creates a new memory group, allocating it's buffers with the provided allocator. + /// + /// The to use. + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The . + /// A new . + /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. + public static MemoryGroup Allocate( + MemoryAllocator allocator, + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + { + Guard.NotNull(allocator, nameof(allocator)); + Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); + + int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; + + if (bufferAlignment > blockCapacityInElements) + { + throw new InvalidMemoryOperationException( + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); + } + + if (totalLength == 0) + { + var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; + return new Owned(buffers0, 0, 0, true); + } + + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; + int bufferLength = numberOfAlignedSegments * bufferAlignment; + if (totalLength > 0 && totalLength < bufferLength) + { + bufferLength = (int)totalLength; + } + + int sizeOfLastBuffer = (int)(totalLength % bufferLength); + long bufferCount = totalLength / bufferLength; + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } + + var buffers = new IMemoryOwner[bufferCount]; + for (int i = 0; i < buffers.Length - 1; i++) + { + buffers[i] = allocator.Allocate(bufferLength, options); + } + + if (bufferCount > 0) + { + buffers[buffers.Length - 1] = allocator.Allocate(sizeOfLastBuffer, options); + } + + return new Owned(buffers, bufferLength, totalLength, true); + } + + public static MemoryGroup Wrap(params Memory[] source) + { + int bufferLength = source.Length > 0 ? source[0].Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source[i].Length != bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source[source.Length - 1].Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Length : 0; + + return new Consumed(source, bufferLength, totalLength); + } + + public static MemoryGroup Wrap(params IMemoryOwner[] source) + { + int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source[i].Memory.Length != bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source[source.Length - 1].Memory.Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Memory.Length : 0; + + return new Owned(source, bufferLength, totalLength, false); + } + + /// + /// Swaps the contents of 'target' with 'source' if the buffers are allocated (1), + /// copies the contents of 'source' to 'target' otherwise (2). + /// Groups should be of same TotalLength in case 2. + /// + public static bool SwapOrCopyContent(MemoryGroup target, MemoryGroup source) + { + if (source is Owned ownedSrc && ownedSrc.Swappable && + target is Owned ownedTarget && ownedTarget.Swappable) + { + Owned.SwapContents(ownedTarget, ownedSrc); + return true; + } + else + { + if (target.TotalLength != source.TotalLength) + { + throw new InvalidMemoryOperationException( + "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); + } + + source.CopyTo(target); + return false; + } + } + } +} diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs new file mode 100644 index 0000000000..c1d5c5d416 --- /dev/null +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Exception thrown when the library detects an invalid memory allocation request, + /// or an attempt has been made to use an invalidated . + /// + public class InvalidMemoryOperationException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + /// The exception message text. + public InvalidMemoryOperationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + public InvalidMemoryOperationException() + { + } + } +} diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index b596351b5f..22d1bddd2f 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -1,18 +1,25 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Buffers; -using SixLabors.Memory; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Memory { /// /// Extension methods for . /// - internal static class MemoryAllocatorExtensions + public static class MemoryAllocatorExtensions { + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of x elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer width. + /// The buffer height. + /// The allocation options. + /// The . public static Buffer2D Allocate2D( this MemoryAllocator memoryAllocator, int width, @@ -20,12 +27,20 @@ namespace SixLabors.ImageSharp.Memory AllocationOptions options = AllocationOptions.None) where T : struct { - IMemoryOwner buffer = memoryAllocator.Allocate(width * height, options); - var memorySource = new MemorySource(buffer, true); - - return new Buffer2D(memorySource, width, height); + long groupLength = (long)width * height; + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + return new Buffer2D(memoryGroup, width, height); } + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of width x height elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer size. + /// The allocation options. + /// The . public static Buffer2D Allocate2D( this MemoryAllocator memoryAllocator, Size size, @@ -33,15 +48,31 @@ namespace SixLabors.ImageSharp.Memory where T : struct => Allocate2D(memoryAllocator, size.Width, size.Height, options); + internal static Buffer2D Allocate2DOveraligned( + this MemoryAllocator memoryAllocator, + int width, + int height, + int alignmentMultiplier, + AllocationOptions options = AllocationOptions.None) + where T : struct + { + long groupLength = (long)width * height; + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup( + groupLength, + width * alignmentMultiplier, + options); + return new Buffer2D(memoryGroup, width, height); + } + /// - /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). /// - /// The + /// The . /// Pixel count in the row - /// The pixel size in bytes, eg. 3 for RGB - /// The padding - /// A - public static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + /// The pixel size in bytes, eg. 3 for RGB. + /// The padding. + /// A . + internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer( this MemoryAllocator memoryAllocator, int width, int pixelSizeInBytes, @@ -50,5 +81,22 @@ namespace SixLabors.ImageSharp.Memory int length = (width * pixelSizeInBytes) + paddingInBytes; return memoryAllocator.AllocateManagedByteBuffer(length); } + + /// + /// Allocates a . + /// + /// The to use. + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The . + /// A new . + /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. + internal static MemoryGroup AllocateGroup( + this MemoryAllocator memoryAllocator, + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + where T : struct + => MemoryGroup.Allocate(memoryAllocator, totalLength, bufferAlignment, options); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/MemorySource.cs b/src/ImageSharp/Memory/MemorySource.cs deleted file mode 100644 index f0b0ab0281..0000000000 --- a/src/ImageSharp/Memory/MemorySource.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Holds a that is either OWNED or CONSUMED. - /// When the memory is being owned, the instance is also known. - /// Implements content transfer logic in that depends on the ownership status. - /// This is needed to transfer the contents of a temporary - /// to a persistent without copying the buffer. - /// - /// - /// For a deeper understanding of the owner/consumer model, check out the following docs:
- /// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6 - /// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T - ///
- internal struct MemorySource : IDisposable - { - /// - /// Initializes a new instance of the struct - /// by wrapping an existing . - /// - /// The to wrap - /// - /// A value indicating whether is an internal memory source managed by ImageSharp. - /// Eg. allocated by a . - /// - public MemorySource(IMemoryOwner memoryOwner, bool isInternalMemorySource) - { - this.MemoryOwner = memoryOwner; - this.Memory = memoryOwner.Memory; - this.HasSwappableContents = isInternalMemorySource; - } - - public MemorySource(Memory memory) - { - this.Memory = memory; - this.MemoryOwner = null; - this.HasSwappableContents = false; - } - - public IMemoryOwner MemoryOwner { get; private set; } - - public Memory Memory { get; private set; } - - /// - /// Gets a value indicating whether we are allowed to swap the contents of this buffer - /// with an other instance. - /// The value is true only and only if is present, - /// and it's coming from an internal source managed by ImageSharp (). - /// - public bool HasSwappableContents { get; } - - public Span GetSpan() => this.Memory.Span; - - public void Clear() => this.Memory.Span.Clear(); - - /// - /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), - /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! - /// - public static void SwapOrCopyContent(ref MemorySource destination, ref MemorySource source) - { - if (source.HasSwappableContents && destination.HasSwappableContents) - { - SwapContents(ref destination, ref source); - } - else - { - if (destination.Memory.Length != source.Memory.Length) - { - throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); - } - - source.Memory.CopyTo(destination.Memory); - } - } - - /// - public void Dispose() - { - this.MemoryOwner?.Dispose(); - } - - private static void SwapContents(ref MemorySource a, ref MemorySource b) - { - IMemoryOwner tempOwner = a.MemoryOwner; - Memory tempMemory = a.Memory; - - a.MemoryOwner = b.MemoryOwner; - a.Memory = b.Memory; - - b.MemoryOwner = tempOwner; - b.Memory = tempMemory; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs index 815918754a..c2962cfe97 100644 --- a/src/ImageSharp/Memory/RowInterval.cs +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -1,35 +1,38 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Memory { /// /// Represents an interval of rows in a and/or /// - internal readonly struct RowInterval : IEquatable + /// + /// Before RC1, this class might be target of API changes, use it on your own risk! + /// + public readonly struct RowInterval : IEquatable { /// /// Initializes a new instance of the struct. /// + /// The inclusive minimum row. + /// The exclusive maximum row. public RowInterval(int min, int max) { - DebugGuard.MustBeLessThan(min, max, nameof(min)); + Guard.MustBeLessThan(min, max, nameof(min)); this.Min = min; this.Max = max; } /// - /// Gets the INCLUSIVE minimum. + /// Gets the inclusive minimum row. /// public int Min { get; } /// - /// Gets the EXCLUSIVE maximum. + /// Gets the exclusive maximum row. /// public int Max { get; } @@ -38,33 +41,48 @@ namespace SixLabors.ImageSharp.Memory ///
public int Height => this.Max - this.Min; + /// + /// Returns a boolean indicating whether the given two -s are equal. + /// + /// The first to compare. + /// The second to compare. + /// True if the given -s are equal; False otherwise. public static bool operator ==(RowInterval left, RowInterval right) { return left.Equals(right); } + /// + /// Returns a boolean indicating whether the given two -s are not equal. + /// + /// The first to compare. + /// The second to compare. + /// True if the given -s are not equal; False otherwise. public static bool operator !=(RowInterval left, RowInterval right) { return !left.Equals(right); } /// - public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; - - public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); - - public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); - public bool Equals(RowInterval other) { return this.Min == other.Min && this.Max == other.Max; } + /// public override bool Equals(object obj) { return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); } + /// public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); + + /// + public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; + + internal RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); + + internal RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs new file mode 100644 index 0000000000..31825b7b41 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); +} diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs new file mode 100644 index 0000000000..023606f521 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsInplaceDelegate(Span data); +} diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs deleted file mode 100644 index b3751bfbdc..0000000000 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Metadata -{ - /// - /// Encapsulates the metadata of an image. - /// - public sealed class ImageMetadata : IDeepCloneable - { - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 . - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 . - /// - public const double DefaultVerticalResolution = 96; - - /// - /// The default pixel resolution units. - /// The default value is . - /// - public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; - - private readonly Dictionary formatMetadata = new Dictionary(); - private double horizontalResolution; - private double verticalResolution; - - /// - /// Initializes a new instance of the class. - /// - internal ImageMetadata() - { - this.horizontalResolution = DefaultHorizontalResolution; - this.verticalResolution = DefaultVerticalResolution; - this.ResolutionUnits = DefaultPixelResolutionUnits; - } - - /// - /// Initializes a new instance of the class - /// by making a copy from other metadata. - /// - /// - /// The other to create this instance from. - /// - private ImageMetadata(ImageMetadata other) - { - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.ResolutionUnits = other.ResolutionUnits; - - foreach (KeyValuePair meta in other.formatMetadata) - { - this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); - } - - this.ExifProfile = other.ExifProfile?.DeepClone(); - this.IccProfile = other.IccProfile?.DeepClone(); - } - - /// - /// Gets or sets the resolution of the image in x- direction. - /// It is defined as the number of dots per inch and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution - { - get => this.horizontalResolution; - - set - { - if (value > 0) - { - this.horizontalResolution = value; - } - } - } - - /// - /// Gets or sets the resolution of the image in y- direction. - /// It is defined as the number of dots per inch and should be an positive value. - /// - /// The density of the image in y- direction. - public double VerticalResolution - { - get => this.verticalResolution; - - set - { - if (value > 0) - { - this.verticalResolution = value; - } - } - } - - /// - /// Gets or sets unit of measure used when reporting resolution. - /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity - /// 01 : Pixels per inch (2.54 cm) - /// 02 : Pixels per centimeter - /// 03 : Pixels per meter - /// - public PixelResolutionUnit ResolutionUnits { get; set; } - - /// - /// Gets or sets the Exif profile. - /// - public ExifProfile ExifProfile { get; set; } - - /// - /// Gets or sets the list of ICC profiles. - /// - public IccProfile IccProfile { get; set; } - - /// - /// Gets the metadata value associated with the specified key. - /// - /// The type of metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatMetadata GetFormatMetadata(IImageFormat key) - where TFormatMetadata : class, IDeepCloneable - { - if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) - { - return (TFormatMetadata)meta; - } - - TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); - this.formatMetadata[key] = newMeta; - return newMeta; - } - - /// - public ImageMetadata DeepClone() => new ImageMetadata(this); - - /// - /// Synchronizes the profiles with the current metadata. - /// - internal void SyncProfiles() => this.ExifProfile?.Sync(this); - } -} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs deleted file mode 100644 index c7112c47a3..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - internal static class ExifConstants - { - public static readonly byte[] LittleEndianByteOrderMarker = - { - (byte)'I', - (byte)'I', - 0x2A, - 0x00, - }; - - public static readonly byte[] BigEndianByteOrderMarker = - { - (byte)'M', - (byte)'M', - 0x00, - 0x2A - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs deleted file mode 100644 index 83e7f7fe8b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Specifies exif data types. - /// - public enum ExifDataType - { - /// - /// Unknown - /// - Unknown = 0, - - /// - /// An 8-bit unsigned integer. - /// - Byte = 1, - - /// - /// An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. - /// - Ascii = 2, - - /// - /// A 16-bit (2-byte) unsigned integer. - /// - Short = 3, - - /// - /// A 32-bit (4-byte) unsigned integer. - /// - Long = 4, - - /// - /// Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. - /// - Rational = 5, - - /// - /// An 8-bit signed integer. - /// - SignedByte = 6, - - /// - /// An 8-bit byte that can take any value depending on the field definition. - /// - Undefined = 7, - - /// - /// A 16-bit (2-byte) signed integer. - /// - SignedShort = 8, - - /// - /// A 32-bit (4-byte) signed integer (2's complement notation). - /// - SignedLong = 9, - - /// - /// Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. - /// - SignedRational = 10, - - /// - /// A 32-bit floating point value. - /// - SingleFloat = 11, - - /// - /// A 64-bit floating point value. - /// - DoubleFloat = 12 - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs deleted file mode 100644 index d22dc730f9..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Specifies which parts will be written when the profile is added to an image. - /// - [Flags] - public enum ExifParts - { - /// - /// None - /// - None = 0, - - /// - /// IfdTags - /// - IfdTags = 1, - - /// - /// ExifTags - /// - ExifTags = 4, - - /// - /// GPSTags - /// - GPSTags = 8, - - /// - /// All - /// - All = IfdTags | ExifTags | GPSTags - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs deleted file mode 100644 index 3d90cb3a9e..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.IO; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Represents an EXIF profile providing access to the collection of values. - /// - public sealed class ExifProfile : IDeepCloneable - { - /// - /// The byte array to read the EXIF profile from. - /// - private readonly byte[] data; - - /// - /// The collection of EXIF values - /// - private List values; - - /// - /// The thumbnail offset position in the byte stream - /// - private int thumbnailOffset; - - /// - /// The thumbnail length in the byte stream - /// - private int thumbnailLength; - - /// - /// Initializes a new instance of the class. - /// - public ExifProfile() - : this((byte[])null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The byte array to read the EXIF profile from. - public ExifProfile(byte[] data) - { - this.Parts = ExifParts.All; - this.data = data; - this.InvalidTags = Array.Empty(); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another EXIF profile. - /// - /// The other EXIF profile, where the clone should be made from. - private ExifProfile(ExifProfile other) - { - this.Parts = other.Parts; - this.thumbnailLength = other.thumbnailLength; - this.thumbnailOffset = other.thumbnailOffset; - - this.InvalidTags = other.InvalidTags.Count > 0 - ? new List(other.InvalidTags) - : (IReadOnlyList)Array.Empty(); - - if (other.values != null) - { - this.values = new List(other.Values.Count); - - foreach (ExifValue value in other.Values) - { - this.values.Add(value.DeepClone()); - } - } - - if (other.data != null) - { - this.data = new byte[other.data.Length]; - other.data.AsSpan().CopyTo(this.data); - } - } - - /// - /// Gets or sets which parts will be written when the profile is added to an image. - /// - public ExifParts Parts { get; set; } - - /// - /// Gets the tags that where found but contained an invalid value. - /// - public IReadOnlyList InvalidTags { get; private set; } - - /// - /// Gets the values of this EXIF profile. - /// - public IReadOnlyList Values - { - get - { - this.InitializeValues(); - return this.values; - } - } - - /// - /// Returns the thumbnail in the EXIF profile when available. - /// - /// The pixel format. - /// - /// The . - /// - public Image CreateThumbnail() - where TPixel : struct, IPixel - { - this.InitializeValues(); - - if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) - { - return null; - } - - if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) - { - return null; - } - - using (var memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) - { - return Image.Load(memStream); - } - } - - /// - /// Returns the value with the specified tag. - /// - /// The tag of the EXIF value. - /// - /// The . - /// - public ExifValue GetValue(ExifTag tag) - { - foreach (ExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - return exifValue; - } - } - - return null; - } - - /// - /// Conditionally returns the value of the tag if it exists. - /// - /// The tag of the EXIF value. - /// The value of the tag, if found. - /// - /// The . - /// - public bool TryGetValue(ExifTag tag, out ExifValue value) - { - foreach (ExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - value = exifValue; - - return true; - } - } - - value = default; - - return false; - } - - /// - /// Removes the value with the specified tag. - /// - /// The tag of the EXIF value. - /// - /// The . - /// - public bool RemoveValue(ExifTag tag) - { - this.InitializeValues(); - - for (int i = 0; i < this.values.Count; i++) - { - if (this.values[i].Tag == tag) - { - this.values.RemoveAt(i); - return true; - } - } - - return false; - } - - /// - /// Sets the value of the specified tag. - /// - /// The tag of the EXIF value. - /// The value. - public void SetValue(ExifTag tag, object value) - { - for (int i = 0; i < this.Values.Count; i++) - { - if (this.values[i].Tag == tag) - { - this.values[i] = this.values[i].WithValue(value); - - return; - } - } - - var newExifValue = ExifValue.Create(tag, value); - - this.values.Add(newExifValue); - } - - /// - /// Converts this instance to a byte array. - /// - /// The - public byte[] ToByteArray() - { - if (this.values is null) - { - return this.data; - } - - if (this.values.Count == 0) - { - return null; - } - - var writer = new ExifWriter(this.values, this.Parts); - return writer.GetData(); - } - - /// - public ExifProfile DeepClone() => new ExifProfile(this); - - /// - /// Synchronizes the profiles with the specified metadata. - /// - /// The metadata. - internal void Sync(ImageMetadata metadata) - { - this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); - this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); - } - - private void SyncResolution(ExifTag tag, double resolution) - { - ExifValue value = this.GetValue(tag); - - if (value is null) - { - return; - } - - if (value.IsArray || value.DataType != ExifDataType.Rational) - { - this.RemoveValue(value.Tag); - } - - var newResolution = new Rational(resolution, false); - this.SetValue(tag, newResolution); - } - - private void InitializeValues() - { - if (this.values != null) - { - return; - } - - if (this.data is null) - { - this.values = new List(); - return; - } - - var reader = new ExifReader(this.data); - - this.values = reader.ReadValues(); - - this.InvalidTags = reader.InvalidTags.Count > 0 - ? new List(reader.InvalidTags) - : (IReadOnlyList)Array.Empty(); - - this.thumbnailOffset = (int)reader.ThumbnailOffset; - this.thumbnailLength = (int)reader.ThumbnailLength; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs deleted file mode 100644 index 77c1cf2eab..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Reads and parses EXIF data from a byte array. - /// - internal sealed class ExifReader - { - private List invalidTags; - private readonly byte[] exifData; - private int position; - private bool isBigEndian; - private uint exifOffset; - private uint gpsOffset; - - public ExifReader(byte[] exifData) - { - this.exifData = exifData ?? throw new ArgumentNullException(nameof(exifData)); - } - - private delegate TDataType ConverterMethod(ReadOnlySpan data); - - /// - /// Gets the invalid tags. - /// - public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); - - /// - /// Gets the thumbnail length in the byte stream. - /// - public uint ThumbnailLength { get; private set; } - - /// - /// Gets the thumbnail offset position in the byte stream. - /// - public uint ThumbnailOffset { get; private set; } - - /// - /// Gets the remaining length. - /// - private int RemainingLength - { - get - { - if (this.position >= this.exifData.Length) - { - return 0; - } - - return this.exifData.Length - this.position; - } - } - - /// - /// Reads and returns the collection of EXIF values. - /// - /// - /// The . - /// - public List ReadValues() - { - var values = new List(); - - // II == 0x4949 - this.isBigEndian = !(this.ReadUInt16() == 0x4949); - - if (this.ReadUInt16() != 0x002A) - { - return values; - } - - uint ifdOffset = this.ReadUInt32(); - this.AddValues(values, ifdOffset); - - uint thumbnailOffset = this.ReadUInt32(); - this.GetThumbnail(thumbnailOffset); - - if (this.exifOffset != 0) - { - this.AddValues(values, this.exifOffset); - } - - if (this.gpsOffset != 0) - { - this.AddValues(values, this.gpsOffset); - } - - return values; - } - - private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) - { - int dataTypeSize = (int)ExifValue.GetSize(dataType); - int length = data.Length / dataTypeSize; - - var result = new TDataType[length]; - - for (int i = 0; i < length; i++) - { - ReadOnlySpan buffer = data.Slice(i * dataTypeSize, dataTypeSize); - - result.SetValue(converter(buffer), i); - } - - return result; - } - - private byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; - - private string ConvertToString(ReadOnlySpan buffer) - { - int nullCharIndex = buffer.IndexOf((byte)0); - - if (nullCharIndex > -1) - { - buffer = buffer.Slice(0, nullCharIndex); - } - - return Encoding.UTF8.GetString(buffer); - } - - /// - /// Adds the collection of EXIF values to the reader. - /// - /// The values. - /// The index. - private void AddValues(List values, uint index) - { - if (index > (uint)this.exifData.Length) - { - return; - } - - this.position = (int)index; - int count = this.ReadUInt16(); - - for (int i = 0; i < count; i++) - { - if (!this.TryReadValue(out ExifValue value)) - { - continue; - } - - bool duplicate = false; - foreach (ExifValue val in values) - { - if (val.Tag == value.Tag) - { - duplicate = true; - break; - } - } - - if (duplicate) - { - continue; - } - - if (value.Tag == ExifTag.SubIFDOffset) - { - if (value.DataType == ExifDataType.Long) - { - this.exifOffset = (uint)value.Value; - } - } - else if (value.Tag == ExifTag.GPSIFDOffset) - { - if (value.DataType == ExifDataType.Long) - { - this.gpsOffset = (uint)value.Value; - } - } - else - { - values.Add(value); - } - } - } - - private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, uint numberOfComponents) - { - if (buffer.Length == 0) - { - return null; - } - - switch (dataType) - { - case ExifDataType.Unknown: - return null; - case ExifDataType.Ascii: - return this.ConvertToString(buffer); - case ExifDataType.Byte: - if (numberOfComponents == 1) - { - return this.ConvertToByte(buffer); - } - - return buffer.ToArray(); - case ExifDataType.DoubleFloat: - if (numberOfComponents == 1) - { - return this.ConvertToDouble(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToDouble); - case ExifDataType.Long: - if (numberOfComponents == 1) - { - return this.ConvertToUInt32(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt32); - case ExifDataType.Rational: - if (numberOfComponents == 1) - { - return this.ToRational(buffer); - } - - return ToArray(dataType, buffer, this.ToRational); - case ExifDataType.Short: - if (numberOfComponents == 1) - { - return this.ConvertToShort(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToShort); - case ExifDataType.SignedByte: - if (numberOfComponents == 1) - { - return this.ConvertToSignedByte(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSignedByte); - case ExifDataType.SignedLong: - if (numberOfComponents == 1) - { - return this.ConvertToInt32(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToInt32); - case ExifDataType.SignedRational: - if (numberOfComponents == 1) - { - return this.ToSignedRational(buffer); - } - - return ToArray(dataType, buffer, this.ToSignedRational); - case ExifDataType.SignedShort: - if (numberOfComponents == 1) - { - return this.ConvertToSignedShort(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSignedShort); - case ExifDataType.SingleFloat: - if (numberOfComponents == 1) - { - return this.ConvertToSingle(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSingle); - case ExifDataType.Undefined: - if (numberOfComponents == 1) - { - return this.ConvertToByte(buffer); - } - - return buffer.ToArray(); - default: - throw new NotSupportedException(); - } - } - - private bool TryReadValue(out ExifValue exifValue) - { - // 2 | 2 | 4 | 4 - // tag | type | count | value offset - if (this.RemainingLength < 12) - { - exifValue = default; - - return false; - } - - ExifTag tag = this.ToEnum(this.ReadUInt16(), ExifTag.Unknown); - uint type = this.ReadUInt16(); - - // Ensure that the data type is valid - if (type == 0 || type > 12) - { - exifValue = new ExifValue(tag, ExifDataType.Unknown, null, false); - - return true; - } - - var dataType = (ExifDataType)type; - - object value; - - uint numberOfComponents = this.ReadUInt32(); - - // Issue #132: ExifDataType == Undefined is treated like a byte array. - // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) - if (dataType == ExifDataType.Undefined && numberOfComponents == 0) - { - numberOfComponents = 4; - } - - uint size = numberOfComponents * ExifValue.GetSize(dataType); - - this.TryReadSpan(4, out ReadOnlySpan offsetBuffer); - - if (size > 4) - { - int oldIndex = this.position; - - uint newIndex = this.ConvertToUInt32(offsetBuffer); - - // Ensure that the new index does not overrun the data - if (newIndex > int.MaxValue) - { - this.AddInvalidTag(tag); - - exifValue = default; - - return false; - } - - this.position = (int)newIndex; - - if (this.RemainingLength < size) - { - this.AddInvalidTag(tag); - - this.position = oldIndex; - - exifValue = default; - - return false; - } - - this.TryReadSpan((int)size, out ReadOnlySpan dataBuffer); - - value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); - this.position = oldIndex; - } - else - { - value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents); - } - - exifValue = new ExifValue(tag, dataType, value, isArray: value != null && numberOfComponents != 1); - - return true; - } - - private void AddInvalidTag(ExifTag tag) - { - if (this.invalidTags is null) - { - this.invalidTags = new List(); - } - - this.invalidTags.Add(tag); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private TEnum ToEnum(int value, TEnum defaultValue) - where TEnum : struct, Enum - { - if (EnumHelper.IsDefined(value)) - { - return Unsafe.As(ref value); - } - - return defaultValue; - } - - private bool TryReadSpan(int length, out ReadOnlySpan span) - { - if (this.RemainingLength < length) - { - span = default; - - return false; - } - - span = new ReadOnlySpan(this.exifData, this.position, length); - - this.position += length; - - return true; - } - - private uint ReadUInt32() - { - // Known as Long in Exif Specification - return this.TryReadSpan(4, out ReadOnlySpan span) - ? this.ConvertToUInt32(span) - : default; - } - - private ushort ReadUInt16() - { - return this.TryReadSpan(2, out ReadOnlySpan span) - ? this.ConvertToShort(span) - : default; - } - - private void GetThumbnail(uint offset) - { - var values = new List(); - this.AddValues(values, offset); - - foreach (ExifValue value in values) - { - if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) - { - this.ThumbnailOffset = (uint)value.Value; - } - else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) - { - this.ThumbnailLength = (uint)value.Value; - } - } - } - - private double ConvertToDouble(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - long intValue = this.isBigEndian - ? BinaryPrimitives.ReadInt64BigEndian(buffer) - : BinaryPrimitives.ReadInt64LittleEndian(buffer); - - return Unsafe.As(ref intValue); - } - - private uint ConvertToUInt32(ReadOnlySpan buffer) - { - // Known as Long in Exif Specification - if (buffer.Length < 4) - { - return default; - } - - return this.isBigEndian - ? BinaryPrimitives.ReadUInt32BigEndian(buffer) - : BinaryPrimitives.ReadUInt32LittleEndian(buffer); - } - - private ushort ConvertToShort(ReadOnlySpan buffer) - { - if (buffer.Length < 2) - { - return default; - } - - return this.isBigEndian - ? BinaryPrimitives.ReadUInt16BigEndian(buffer) - : BinaryPrimitives.ReadUInt16LittleEndian(buffer); - } - - private float ConvertToSingle(ReadOnlySpan buffer) - { - if (buffer.Length < 4) - { - return default; - } - - int intValue = this.isBigEndian - ? BinaryPrimitives.ReadInt32BigEndian(buffer) - : BinaryPrimitives.ReadInt32LittleEndian(buffer); - - return Unsafe.As(ref intValue); - } - - private Rational ToRational(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - uint numerator = this.ConvertToUInt32(buffer.Slice(0, 4)); - uint denominator = this.ConvertToUInt32(buffer.Slice(4, 4)); - - return new Rational(numerator, denominator, false); - } - - private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); - - private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification - { - if (buffer.Length < 4) - { - return default; - } - - return this.isBigEndian - ? BinaryPrimitives.ReadInt32BigEndian(buffer) - : BinaryPrimitives.ReadInt32LittleEndian(buffer); - } - - private SignedRational ToSignedRational(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - int numerator = this.ConvertToInt32(buffer.Slice(0, 4)); - int denominator = this.ConvertToInt32(buffer.Slice(4, 4)); - - return new SignedRational(numerator, denominator, false); - } - - private short ConvertToSignedShort(ReadOnlySpan buffer) - { - if (buffer.Length < 2) - { - return default; - } - - return this.isBigEndian - ? BinaryPrimitives.ReadInt16BigEndian(buffer) - : BinaryPrimitives.ReadInt16LittleEndian(buffer); - } - - private sealed class EnumHelper - where TEnum : struct, Enum - { - private static readonly int[] Values = Enum.GetValues(typeof(TEnum)).Cast() - .Select(e => Convert.ToInt32(e)).OrderBy(e => e).ToArray(); - - [MethodImpl(InliningOptions.ShortMethod)] - public static bool IsDefined(int value) - { - return Array.BinarySearch(Values, value) >= 0; - } - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs deleted file mode 100644 index ddd4591fac..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs +++ /dev/null @@ -1,1544 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// All exif tags from the Exif standard 2.2 - /// Descriptions from: - /// - public enum ExifTag - { - /// - /// Unknown - /// - Unknown = 0xFFFF, - - /// - /// SubIFDOffset - /// - SubIFDOffset = 0x8769, - - /// - /// GPSIFDOffset - /// - GPSIFDOffset = 0x8825, - - /// - /// SubfileType - /// - [ExifTagDescription(0U, "Full-resolution Image")] - [ExifTagDescription(1U, "Reduced-resolution image")] - [ExifTagDescription(2U, "Single page of multi-page image")] - [ExifTagDescription(3U, "Single page of multi-page reduced-resolution image")] - [ExifTagDescription(4U, "Transparency mask")] - [ExifTagDescription(5U, "Transparency mask of reduced-resolution image")] - [ExifTagDescription(6U, "Transparency mask of multi-page image")] - [ExifTagDescription(7U, "Transparency mask of reduced-resolution multi-page image")] - [ExifTagDescription(0x10001U, "Alternate reduced-resolution image ")] - SubfileType = 0x00FE, - - /// - /// OldSubfileType - /// - [ExifTagDescription((ushort)1, "Full-resolution Image")] - [ExifTagDescription((ushort)2, "Reduced-resolution image")] - [ExifTagDescription((ushort)3, "Single page of multi-page image")] - OldSubfileType = 0x00FF, - - /// - /// ImageWidth - /// - ImageWidth = 0x0100, - - /// - /// ImageLength - /// - ImageLength = 0x0101, - - /// - /// BitsPerSample - /// - BitsPerSample = 0x0102, - - /// - /// Compression - /// - [ExifTagDescription((ushort)1, "Uncompressed")] - [ExifTagDescription((ushort)2, "CCITT 1D")] - [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] - [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] - [ExifTagDescription((ushort)5, "LZW")] - [ExifTagDescription((ushort)6, "JPEG (old-style)")] - [ExifTagDescription((ushort)7, "JPEG")] - [ExifTagDescription((ushort)8, "Adobe Deflate")] - [ExifTagDescription((ushort)9, "JBIG B&W")] - [ExifTagDescription((ushort)10, "JBIG Color")] - [ExifTagDescription((ushort)99, "JPEG")] - [ExifTagDescription((ushort)262, "Kodak 262")] - [ExifTagDescription((ushort)32766, "Next")] - [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] - [ExifTagDescription((ushort)32769, "Packed RAW")] - [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] - [ExifTagDescription((ushort)32771, "CCIRLEW")] - [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] - [ExifTagDescription((ushort)32773, "PackBits")] - [ExifTagDescription((ushort)32809, "Thunderscan")] - [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] - [ExifTagDescription((ushort)32895, "IT8CTPAD")] - [ExifTagDescription((ushort)32896, "IT8LW")] - [ExifTagDescription((ushort)32897, "IT8MP")] - [ExifTagDescription((ushort)32898, "IT8BL")] - [ExifTagDescription((ushort)32908, "PixarFilm")] - [ExifTagDescription((ushort)32909, "PixarLog")] - [ExifTagDescription((ushort)32946, "Deflate")] - [ExifTagDescription((ushort)32947, "DCS")] - [ExifTagDescription((ushort)34661, "JBIG")] - [ExifTagDescription((ushort)34676, "SGILog")] - [ExifTagDescription((ushort)34677, "SGILog24")] - [ExifTagDescription((ushort)34712, "JPEG 2000")] - [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] - [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] - [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] - [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] - [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] - [ExifTagDescription((ushort)34892, "Lossy JPEG")] - [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] - [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] - Compression = 0x0103, - - /// - /// PhotometricInterpretation - /// - [ExifTagDescription((ushort)0, "WhiteIsZero")] - [ExifTagDescription((ushort)1, "BlackIsZero")] - [ExifTagDescription((ushort)2, "RGB")] - [ExifTagDescription((ushort)3, "RGB Palette")] - [ExifTagDescription((ushort)4, "Transparency Mask")] - [ExifTagDescription((ushort)5, "CMYK")] - [ExifTagDescription((ushort)6, "YCbCr")] - [ExifTagDescription((ushort)8, "CIELab")] - [ExifTagDescription((ushort)9, "ICCLab")] - [ExifTagDescription((ushort)10, "TULab")] - [ExifTagDescription((ushort)32803, "Color Filter Array")] - [ExifTagDescription((ushort)32844, "Pixar LogL")] - [ExifTagDescription((ushort)32845, "Pixar LogLuv")] - [ExifTagDescription((ushort)34892, "Linear Raw")] - PhotometricInterpretation = 0x0106, - - /// - /// Thresholding - /// - [ExifTagDescription((ushort)1, "No dithering or halftoning")] - [ExifTagDescription((ushort)2, "Ordered dither or halftone")] - [ExifTagDescription((ushort)3, "Randomized dither")] - Thresholding = 0x0107, - - /// - /// CellWidth - /// - CellWidth = 0x0108, - - /// - /// CellLength - /// - CellLength = 0x0109, - - /// - /// FillOrder - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Reversed")] - FillOrder = 0x010A, - - /// - /// DocumentName - /// - DocumentName = 0x010D, - - /// - /// ImageDescription - /// - ImageDescription = 0x010E, - - /// - /// Make - /// - Make = 0x010F, - - /// - /// Model - /// - Model = 0x0110, - - /// - /// StripOffsets - /// - StripOffsets = 0x0111, - - /// - /// Orientation - /// - [ExifTagDescription((ushort)1, "Horizontal (normal)")] - [ExifTagDescription((ushort)2, "Mirror horizontal")] - [ExifTagDescription((ushort)3, "Rotate 180")] - [ExifTagDescription((ushort)4, "Mirror vertical")] - [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] - [ExifTagDescription((ushort)6, "Rotate 90 CW")] - [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] - [ExifTagDescription((ushort)8, "Rotate 270 CW")] - Orientation = 0x0112, - - /// - /// SamplesPerPixel - /// - SamplesPerPixel = 0x0115, - - /// - /// RowsPerStrip - /// - RowsPerStrip = 0x0116, - - /// - /// StripByteCounts - /// - StripByteCounts = 0x0117, - - /// - /// MinSampleValue - /// - MinSampleValue = 0x0118, - - /// - /// MaxSampleValue - /// - MaxSampleValue = 0x0119, - - /// - /// XResolution - /// - XResolution = 0x011A, - - /// - /// YResolution - /// - YResolution = 0x011B, - - /// - /// PlanarConfiguration - /// - [ExifTagDescription((ushort)1, "Chunky")] - [ExifTagDescription((ushort)2, "Planar")] - PlanarConfiguration = 0x011C, - - /// - /// PageName - /// - PageName = 0x011D, - - /// - /// XPosition - /// - XPosition = 0x011E, - - /// - /// YPosition - /// - YPosition = 0x011F, - - /// - /// FreeOffsets - /// - FreeOffsets = 0x0120, - - /// - /// FreeByteCounts - /// - FreeByteCounts = 0x0121, - - /// - /// GrayResponseUnit - /// - [ExifTagDescription((ushort)1, "0.1")] - [ExifTagDescription((ushort)2, "0.001")] - [ExifTagDescription((ushort)3, "0.0001")] - [ExifTagDescription((ushort)4, "1e-05")] - [ExifTagDescription((ushort)5, "1e-06")] - GrayResponseUnit = 0x0122, - - /// - /// GrayResponseCurve - /// - GrayResponseCurve = 0x0123, - - /// - /// T4Options - /// - [ExifTagDescription(0U, "2-Dimensional encoding")] - [ExifTagDescription(1U, "Uncompressed")] - [ExifTagDescription(2U, "Fill bits added")] - T4Options = 0x0124, - - /// - /// T6Options - /// - [ExifTagDescription(1U, "Uncompressed")] - T6Options = 0x0125, - - /// - /// ResolutionUnit - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - ResolutionUnit = 0x0128, - - /// - /// PageNumber - /// - PageNumber = 0x0129, - - /// - /// ColorResponseUnit - /// - ColorResponseUnit = 0x012C, - - /// - /// TransferFunction - /// - TransferFunction = 0x012D, - - /// - /// Software - /// - Software = 0x0131, - - /// - /// DateTime - /// - DateTime = 0x0132, - - /// - /// Artist - /// - Artist = 0x013B, - - /// - /// HostComputer - /// - HostComputer = 0x013C, - - /// - /// Predictor - /// - Predictor = 0x013D, - - /// - /// WhitePoint - /// - WhitePoint = 0x013E, - - /// - /// PrimaryChromaticities - /// - PrimaryChromaticities = 0x013F, - - /// - /// ColorMap - /// - ColorMap = 0x0140, - - /// - /// HalftoneHints - /// - HalftoneHints = 0x0141, - - /// - /// TileWidth - /// - TileWidth = 0x0142, - - /// - /// TileLength - /// - TileLength = 0x0143, - - /// - /// TileOffsets - /// - TileOffsets = 0x0144, - - /// - /// TileByteCounts - /// - TileByteCounts = 0x0145, - - /// - /// BadFaxLines - /// - BadFaxLines = 0x0146, - - /// - /// CleanFaxData - /// - [ExifTagDescription(0U, "Clean")] - [ExifTagDescription(1U, "Regenerated")] - [ExifTagDescription(2U, "Unclean")] - CleanFaxData = 0x0147, - - /// - /// ConsecutiveBadFaxLines - /// - ConsecutiveBadFaxLines = 0x0148, - - /// - /// InkSet - /// - [ExifTagDescription((ushort)1, "CMYK")] - [ExifTagDescription((ushort)2, "Not CMYK")] - InkSet = 0x014C, - - /// - /// InkNames - /// - InkNames = 0x014D, - - /// - /// NumberOfInks - /// - NumberOfInks = 0x014E, - - /// - /// DotRange - /// - DotRange = 0x0150, - - /// - /// TargetPrinter - /// - TargetPrinter = 0x0151, - - /// - /// ExtraSamples - /// - [ExifTagDescription((ushort)0, "Unspecified")] - [ExifTagDescription((ushort)1, "Associated Alpha")] - [ExifTagDescription((ushort)2, "Unassociated Alpha")] - ExtraSamples = 0x0152, - - /// - /// SampleFormat - /// - [ExifTagDescription((ushort)1, "Unsigned")] - [ExifTagDescription((ushort)2, "Signed")] - [ExifTagDescription((ushort)3, "Float")] - [ExifTagDescription((ushort)4, "Undefined")] - [ExifTagDescription((ushort)5, "Complex int")] - [ExifTagDescription((ushort)6, "Complex float")] - SampleFormat = 0x0153, - - /// - /// SMinSampleValue - /// - SMinSampleValue = 0x0154, - - /// - /// SMaxSampleValue - /// - SMaxSampleValue = 0x0155, - - /// - /// TransferRange - /// - TransferRange = 0x0156, - - /// - /// ClipPath - /// - ClipPath = 0x0157, - - /// - /// XClipPathUnits - /// - XClipPathUnits = 0x0158, - - /// - /// YClipPathUnits - /// - YClipPathUnits = 0x0159, - - /// - /// Indexed - /// - [ExifTagDescription((ushort)0, "Not indexed")] - [ExifTagDescription((ushort)1, "Indexed")] - Indexed = 0x015A, - - /// - /// JPEGTables - /// - JPEGTables = 0x015B, - - /// - /// OPIProxy - /// - [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] - [ExifTagDescription((ushort)1, "Higher resolution image exists")] - OPIProxy = 0x015F, - - /// - /// ProfileType - /// - [ExifTagDescription(0U, "Unspecified")] - [ExifTagDescription(1U, "Group 3 FAX")] - ProfileType = 0x0191, - - /// - /// FaxProfile - /// - [ExifTagDescription((byte)0, "Unknown")] - [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] - [ExifTagDescription((byte)2, "Extended B&W lossless, F")] - [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] - [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] - [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] - [ExifTagDescription((byte)6, "Mixed raster content, M")] - [ExifTagDescription((byte)7, "Profile T")] - [ExifTagDescription((byte)255, "Multi Profiles")] - FaxProfile = 0x0192, - - /// - /// CodingMethods - /// - [ExifTagDescription(0UL, "Unspecified compression")] - [ExifTagDescription(1UL, "Modified Huffman")] - [ExifTagDescription(2UL, "Modified Read")] - [ExifTagDescription(4UL, "Modified MR")] - [ExifTagDescription(8UL, "JBIG")] - [ExifTagDescription(16UL, "Baseline JPEG")] - [ExifTagDescription(32UL, "JBIG color")] - CodingMethods = 0x0193, - - /// - /// VersionYear - /// - VersionYear = 0x0194, - - /// - /// ModeNumber - /// - ModeNumber = 0x0195, - - /// - /// Decode - /// - Decode = 0x01B1, - - /// - /// DefaultImageColor - /// - DefaultImageColor = 0x01B2, - - /// - /// T82ptions - /// - T82ptions = 0x01B3, - - /// - /// JPEGProc - /// - [ExifTagDescription((ushort)1, "Baseline")] - [ExifTagDescription((ushort)14, "Lossless")] - JPEGProc = 0x0200, - - /// - /// JPEGInterchangeFormat - /// - JPEGInterchangeFormat = 0x0201, - - /// - /// JPEGInterchangeFormatLength - /// - JPEGInterchangeFormatLength = 0x0202, - - /// - /// JPEGRestartInterval - /// - JPEGRestartInterval = 0x0203, - - /// - /// JPEGLosslessPredictors - /// - JPEGLosslessPredictors = 0x0205, - - /// - /// JPEGPointTransforms - /// - JPEGPointTransforms = 0x0206, - - /// - /// JPEGQTables - /// - JPEGQTables = 0x0207, - - /// - /// JPEGDCTables - /// - JPEGDCTables = 0x0208, - - /// - /// JPEGACTables - /// - JPEGACTables = 0x0209, - - /// - /// YCbCrCoefficients - /// - YCbCrCoefficients = 0x0211, - - /// - /// YCbCrSubsampling - /// - YCbCrSubsampling = 0x0212, - - /// - /// YCbCrPositioning - /// - [ExifTagDescription((ushort)1, "Centered")] - [ExifTagDescription((ushort)2, "Co-sited")] - YCbCrPositioning = 0x0213, - - /// - /// ReferenceBlackWhite - /// - ReferenceBlackWhite = 0x0214, - - /// - /// StripRowCounts - /// - StripRowCounts = 0x022F, - - /// - /// XMP - /// - XMP = 0x02BC, - - /// - /// Rating - /// - Rating = 0x4746, - - /// - /// RatingPercent - /// - RatingPercent = 0x4749, - - /// - /// ImageID - /// - ImageID = 0x800D, - - /// - /// CFARepeatPatternDim - /// - CFARepeatPatternDim = 0x828D, - - /// - /// CFAPattern2 - /// - CFAPattern2 = 0x828E, - - /// - /// BatteryLevel - /// - BatteryLevel = 0x828F, - - /// - /// Copyright - /// - Copyright = 0x8298, - - /// - /// ExposureTime - /// - ExposureTime = 0x829A, - - /// - /// FNumber - /// - FNumber = 0x829D, - - /// - /// MDFileTag - /// - MDFileTag = 0x82A5, - - /// - /// MDScalePixel - /// - MDScalePixel = 0x82A6, - - /// - /// MDLabName - /// - MDLabName = 0x82A8, - - /// - /// MDSampleInfo - /// - MDSampleInfo = 0x82A9, - - /// - /// MDPrepDate - /// - MDPrepDate = 0x82AA, - - /// - /// MDPrepTime - /// - MDPrepTime = 0x82AB, - - /// - /// MDFileUnits - /// - MDFileUnits = 0x82AC, - - /// - /// PixelScale - /// - PixelScale = 0x830E, - - /// - /// IntergraphPacketData - /// - IntergraphPacketData = 0x847E, - - /// - /// IntergraphRegisters - /// - IntergraphRegisters = 0x847F, - - /// - /// IntergraphMatrix - /// - IntergraphMatrix = 0x8480, - - /// - /// ModelTiePoint - /// - ModelTiePoint = 0x8482, - - /// - /// SEMInfo - /// - SEMInfo = 0x8546, - - /// - /// ModelTransform - /// - ModelTransform = 0x85D8, - - /// - /// ImageLayer - /// - ImageLayer = 0x87AC, - - /// - /// ExposureProgram - /// - [ExifTagDescription((ushort)0, "Not Defined")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Program AE")] - [ExifTagDescription((ushort)3, "Aperture-priority AE")] - [ExifTagDescription((ushort)4, "Shutter speed priority AE")] - [ExifTagDescription((ushort)5, "Creative (Slow speed)")] - [ExifTagDescription((ushort)6, "Action (High speed)")] - [ExifTagDescription((ushort)7, "Portrait")] - [ExifTagDescription((ushort)8, "Landscape")] - [ExifTagDescription((ushort)9, "Bulb")] - ExposureProgram = 0x8822, - - /// - /// SpectralSensitivity - /// - SpectralSensitivity = 0x8824, - - /// - /// ISOSpeedRatings - /// - ISOSpeedRatings = 0x8827, - - /// - /// OECF - /// - OECF = 0x8828, - - /// - /// Interlace - /// - Interlace = 0x8829, - - /// - /// TimeZoneOffset - /// - TimeZoneOffset = 0x882A, - - /// - /// SelfTimerMode - /// - SelfTimerMode = 0x882B, - - /// - /// SensitivityType - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] - [ExifTagDescription((ushort)2, "Recommended Exposure Index")] - [ExifTagDescription((ushort)3, "ISO Speed")] - [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] - [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] - [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] - [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] - SensitivityType = 0x8830, - - /// - /// StandardOutputSensitivity - /// - StandardOutputSensitivity = 0x8831, - - /// - /// RecommendedExposureIndex - /// - RecommendedExposureIndex = 0x8832, - - /// - /// ISOSpeed - /// - ISOSpeed = 0x8833, - - /// - /// ISOSpeedLatitudeyyy - /// - ISOSpeedLatitudeyyy = 0x8834, - - /// - /// ISOSpeedLatitudezzz - /// - ISOSpeedLatitudezzz = 0x8835, - - /// - /// FaxRecvParams - /// - FaxRecvParams = 0x885C, - - /// - /// FaxSubaddress - /// - FaxSubaddress = 0x885D, - - /// - /// FaxRecvTime - /// - FaxRecvTime = 0x885E, - - /// - /// ExifVersion - /// - ExifVersion = 0x9000, - - /// - /// DateTimeOriginal - /// - DateTimeOriginal = 0x9003, - - /// - /// DateTimeDigitized - /// - DateTimeDigitized = 0x9004, - - /// - /// OffsetTime - /// - OffsetTime = 0x9010, - - /// - /// OffsetTimeOriginal - /// - OffsetTimeOriginal = 0x9011, - - /// - /// OffsetTimeDigitized - /// - OffsetTimeDigitized = 0x9012, - - /// - /// ComponentsConfiguration - /// - ComponentsConfiguration = 0x9101, - - /// - /// CompressedBitsPerPixel - /// - CompressedBitsPerPixel = 0x9102, - - /// - /// ShutterSpeedValue - /// - ShutterSpeedValue = 0x9201, - - /// - /// ApertureValue - /// - ApertureValue = 0x9202, - - /// - /// BrightnessValue - /// - BrightnessValue = 0x9203, - - /// - /// ExposureBiasValue - /// - ExposureBiasValue = 0x9204, - - /// - /// MaxApertureValue - /// - MaxApertureValue = 0x9205, - - /// - /// SubjectDistance - /// - SubjectDistance = 0x9206, - - /// - /// MeteringMode - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Average")] - [ExifTagDescription((ushort)2, "Center-weighted average")] - [ExifTagDescription((ushort)3, "Spot")] - [ExifTagDescription((ushort)4, "Multi-spot")] - [ExifTagDescription((ushort)5, "Multi-segment")] - [ExifTagDescription((ushort)6, "Partial")] - [ExifTagDescription((ushort)255, "Other")] - MeteringMode = 0x9207, - - /// - /// LightSource - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Daylight")] - [ExifTagDescription((ushort)2, "Fluorescent")] - [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] - [ExifTagDescription((ushort)4, "Flash")] - [ExifTagDescription((ushort)9, "Fine Weather")] - [ExifTagDescription((ushort)10, "Cloudy")] - [ExifTagDescription((ushort)11, "Shade")] - [ExifTagDescription((ushort)12, "Daylight Fluorescent")] - [ExifTagDescription((ushort)13, "Day White Fluorescent")] - [ExifTagDescription((ushort)14, "Cool White Fluorescent")] - [ExifTagDescription((ushort)15, "White Fluorescent")] - [ExifTagDescription((ushort)16, "Warm White Fluorescent")] - [ExifTagDescription((ushort)17, "Standard Light A")] - [ExifTagDescription((ushort)18, "Standard Light B")] - [ExifTagDescription((ushort)19, "Standard Light C")] - [ExifTagDescription((ushort)20, "D55")] - [ExifTagDescription((ushort)21, "D65")] - [ExifTagDescription((ushort)22, "D75")] - [ExifTagDescription((ushort)23, "D50")] - [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] - [ExifTagDescription((ushort)255, "Other")] - LightSource = 0x9208, - - /// - /// Flash - /// - [ExifTagDescription((ushort)0, "No Flash")] - [ExifTagDescription((ushort)1, "Fired")] - [ExifTagDescription((ushort)5, "Fired, Return not detected")] - [ExifTagDescription((ushort)7, "Fired, Return detected")] - [ExifTagDescription((ushort)8, "On, Did not fire")] - [ExifTagDescription((ushort)9, "On, Fired")] - [ExifTagDescription((ushort)13, "On, Return not detected")] - [ExifTagDescription((ushort)15, "On, Return detected")] - [ExifTagDescription((ushort)16, "Off, Did not fire")] - [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] - [ExifTagDescription((ushort)24, "Auto, Did not fire")] - [ExifTagDescription((ushort)25, "Auto, Fired")] - [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] - [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] - [ExifTagDescription((ushort)32, "No flash function")] - [ExifTagDescription((ushort)48, "Off, No flash function")] - [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] - [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)73, "On, Red-eye reduction")] - [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)79, "On, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] - [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] - [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] - [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] - Flash = 0x9209, - - /// - /// FocalLength - /// - FocalLength = 0x920A, - - /// - /// FlashEnergy2 - /// - FlashEnergy2 = 0x920B, - - /// - /// SpatialFrequencyResponse2 - /// - SpatialFrequencyResponse2 = 0x920C, - - /// - /// Noise - /// - Noise = 0x920D, - - /// - /// FocalPlaneXResolution2 - /// - FocalPlaneXResolution2 = 0x920E, - - /// - /// FocalPlaneYResolution2 - /// - FocalPlaneYResolution2 = 0x920F, - - /// - /// FocalPlaneResolutionUnit2 - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit2 = 0x9210, - - /// - /// ImageNumber - /// - ImageNumber = 0x9211, - - /// - /// SecurityClassification - /// - [ExifTagDescription("C", "Confidential")] - [ExifTagDescription("R", "Restricted")] - [ExifTagDescription("S", "Secret")] - [ExifTagDescription("T", "Top Secret")] - [ExifTagDescription("U", "Unclassified")] - SecurityClassification = 0x9212, - - /// - /// ImageHistory - /// - ImageHistory = 0x9213, - - /// - /// SubjectArea - /// - SubjectArea = 0x9214, - - /// - /// ExposureIndex2 - /// - ExposureIndex2 = 0x9215, - - /// - /// TIFFEPStandardID - /// - TIFFEPStandardID = 0x9216, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod2 = 0x9217, - - /// - /// MakerNote - /// - MakerNote = 0x927C, - - /// - /// UserComment - /// - UserComment = 0x9286, - - /// - /// SubsecTime - /// - SubsecTime = 0x9290, - - /// - /// SubsecTimeOriginal - /// - SubsecTimeOriginal = 0x9291, - - /// - /// SubsecTimeDigitized - /// - SubsecTimeDigitized = 0x9292, - - /// - /// ImageSourceData - /// - ImageSourceData = 0x935C, - - /// - /// AmbientTemperature - /// - AmbientTemperature = 0x9400, - - /// - /// Humidity - /// - Humidity = 0x9401, - - /// - /// Pressure - /// - Pressure = 0x9402, - - /// - /// WaterDepth - /// - WaterDepth = 0x9403, - - /// - /// Acceleration - /// - Acceleration = 0x9404, - - /// - /// CameraElevationAngle - /// - CameraElevationAngle = 0x9405, - - /// - /// XPTitle - /// - XPTitle = 0x9C9B, - - /// - /// XPComment - /// - XPComment = 0x9C9C, - - /// - /// XPAuthor - /// - XPAuthor = 0x9C9D, - - /// - /// XPKeywords - /// - XPKeywords = 0x9C9E, - - /// - /// XPSubject - /// - XPSubject = 0x9C9F, - - /// - /// FlashpixVersion - /// - FlashpixVersion = 0xA000, - - /// - /// ColorSpace - /// - [ExifTagDescription((ushort)1, "sRGB")] - [ExifTagDescription((ushort)2, "Adobe RGB")] - [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] - [ExifTagDescription((ushort)65534, "ICC Profile")] - [ExifTagDescription((ushort)65535, "Uncalibrated")] - ColorSpace = 0xA001, - - /// - /// PixelXDimension - /// - PixelXDimension = 0xA002, - - /// - /// PixelYDimension - /// - PixelYDimension = 0xA003, - - /// - /// RelatedSoundFile - /// - RelatedSoundFile = 0xA004, - - /// - /// FlashEnergy - /// - FlashEnergy = 0xA20B, - - /// - /// SpatialFrequencyResponse - /// - SpatialFrequencyResponse = 0xA20C, - - /// - /// FocalPlaneXResolution - /// - FocalPlaneXResolution = 0xA20E, - - /// - /// FocalPlaneYResolution - /// - FocalPlaneYResolution = 0xA20F, - - /// - /// FocalPlaneResolutionUnit - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit = 0xA210, - - /// - /// SubjectLocation - /// - SubjectLocation = 0xA214, - - /// - /// ExposureIndex - /// - ExposureIndex = 0xA215, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod = 0xA217, - - /// - /// FileSource - /// - FileSource = 0xA300, - - /// - /// SceneType - /// - SceneType = 0xA301, - - /// - /// CFAPattern - /// - CFAPattern = 0xA302, - - /// - /// CustomRendered - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Custom")] - CustomRendered = 0xA401, - - /// - /// ExposureMode - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Auto bracket")] - ExposureMode = 0xA402, - - /// - /// WhiteBalance - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - WhiteBalance = 0xA403, - - /// - /// DigitalZoomRatio - /// - DigitalZoomRatio = 0xA404, - - /// - /// FocalLengthIn35mmFilm - /// - FocalLengthIn35mmFilm = 0xA405, - - /// - /// SceneCaptureType - /// - [ExifTagDescription((ushort)0, "Standard")] - [ExifTagDescription((ushort)1, "Landscape")] - [ExifTagDescription((ushort)2, "Portrait")] - [ExifTagDescription((ushort)3, "Night")] - SceneCaptureType = 0xA406, - - /// - /// GainControl - /// - [ExifTagDescription((ushort)0, "None")] - [ExifTagDescription((ushort)1, "Low gain up")] - [ExifTagDescription((ushort)2, "High gain up")] - [ExifTagDescription((ushort)3, "Low gain down")] - [ExifTagDescription((ushort)4, "High gain down")] - GainControl = 0xA407, - - /// - /// Contrast - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Contrast = 0xA408, - - /// - /// Saturation - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Saturation = 0xA409, - - /// - /// Sharpness - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Soft")] - [ExifTagDescription((ushort)2, "Hard")] - Sharpness = 0xA40A, - - /// - /// DeviceSettingDescription - /// - DeviceSettingDescription = 0xA40B, - - /// - /// SubjectDistanceRange - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Macro")] - [ExifTagDescription((ushort)2, "Close")] - [ExifTagDescription((ushort)3, "Distant")] - SubjectDistanceRange = 0xA40C, - - /// - /// ImageUniqueID - /// - ImageUniqueID = 0xA420, - - /// - /// OwnerName - /// - OwnerName = 0xA430, - - /// - /// SerialNumber - /// - SerialNumber = 0xA431, - - /// - /// LensInfo - /// - LensInfo = 0xA432, - - /// - /// LensMake - /// - LensMake = 0xA433, - - /// - /// LensModel - /// - LensModel = 0xA434, - - /// - /// LensSerialNumber - /// - LensSerialNumber = 0xA435, - - /// - /// GDALMetadata - /// - GDALMetadata = 0xA480, - - /// - /// GDALNoData - /// - GDALNoData = 0xA481, - - /// - /// GPSVersionID - /// - GPSVersionID = 0x0000, - - /// - /// GPSLatitudeRef - /// - GPSLatitudeRef = 0x0001, - - /// - /// GPSLatitude - /// - GPSLatitude = 0x0002, - - /// - /// GPSLongitudeRef - /// - GPSLongitudeRef = 0x0003, - - /// - /// GPSLongitude - /// - GPSLongitude = 0x0004, - - /// - /// GPSAltitudeRef - /// - GPSAltitudeRef = 0x0005, - - /// - /// GPSAltitude - /// - GPSAltitude = 0x0006, - - /// - /// GPSTimestamp - /// - GPSTimestamp = 0x0007, - - /// - /// GPSSatellites - /// - GPSSatellites = 0x0008, - - /// - /// GPSStatus - /// - GPSStatus = 0x0009, - - /// - /// GPSMeasureMode - /// - GPSMeasureMode = 0x000A, - - /// - /// GPSDOP - /// - GPSDOP = 0x000B, - - /// - /// GPSSpeedRef - /// - GPSSpeedRef = 0x000C, - - /// - /// GPSSpeed - /// - GPSSpeed = 0x000D, - - /// - /// GPSTrackRef - /// - GPSTrackRef = 0x000E, - - /// - /// GPSTrack - /// - GPSTrack = 0x000F, - - /// - /// GPSImgDirectionRef - /// - GPSImgDirectionRef = 0x0010, - - /// - /// GPSImgDirection - /// - GPSImgDirection = 0x0011, - - /// - /// GPSMapDatum - /// - GPSMapDatum = 0x0012, - - /// - /// GPSDestLatitudeRef - /// - GPSDestLatitudeRef = 0x0013, - - /// - /// GPSDestLatitude - /// - GPSDestLatitude = 0x0014, - - /// - /// GPSDestLongitudeRef - /// - GPSDestLongitudeRef = 0x0015, - - /// - /// GPSDestLongitude - /// - GPSDestLongitude = 0x0016, - - /// - /// GPSDestBearingRef - /// - GPSDestBearingRef = 0x0017, - - /// - /// GPSDestBearing - /// - GPSDestBearing = 0x0018, - - /// - /// GPSDestDistanceRef - /// - GPSDestDistanceRef = 0x0019, - - /// - /// GPSDestDistance - /// - GPSDestDistance = 0x001A, - - /// - /// GPSProcessingMethod - /// - GPSProcessingMethod = 0x001B, - - /// - /// GPSAreaInformation - /// - GPSAreaInformation = 0x001C, - - /// - /// GPSDateStamp - /// - GPSDateStamp = 0x001D, - - /// - /// GPSDifferential - /// - GPSDifferential = 0x001E - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs deleted file mode 100644 index 845e4ee734..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Reflection; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Class that provides a description for an ExifTag value. - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - internal sealed class ExifTagDescriptionAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The value of the exif tag. - /// The description for the value of the exif tag. - public ExifTagDescriptionAttribute(object value, string description) - { - } - - /// - /// Gets the tag description from any custom attributes. - /// - /// The tag. - /// The value. - /// - /// The . - /// - public static string GetDescription(ExifTag tag, object value) - { - FieldInfo field = tag.GetType().GetTypeInfo().GetDeclaredField(tag.ToString()); - - if (field is null) - { - return null; - } - - foreach (CustomAttributeData customAttribute in field.CustomAttributes) - { - object attributeValue = customAttribute.ConstructorArguments[0].Value; - - if (object.Equals(attributeValue, value)) - { - return (string)customAttribute.ConstructorArguments[1].Value; - } - } - - return null; - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs deleted file mode 100644 index 0ed3a43b70..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using static SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - internal static class ExifTags - { - /// - /// The collection if Image File Directory tags - /// - public static readonly ExifTag[] Ifd = - { - SubfileType, - OldSubfileType, - ImageWidth, - ImageLength, - BitsPerSample, - Compression, - PhotometricInterpretation, - Thresholding, - CellWidth, - CellLength, - FillOrder, - DocumentName, - ImageDescription, - Make, - Model, - StripOffsets, - Orientation, - SamplesPerPixel, - RowsPerStrip, - StripByteCounts, - MinSampleValue, - MaxSampleValue, - XResolution, - YResolution, - PlanarConfiguration, - PageName, - XPosition, - YPosition, - FreeOffsets, - FreeByteCounts, - GrayResponseUnit, - GrayResponseCurve, - T4Options, - T6Options, - ResolutionUnit, - PageNumber, - ColorResponseUnit, - TransferFunction, - Software, - DateTime, - Artist, - HostComputer, - Predictor, - WhitePoint, - PrimaryChromaticities, - ColorMap, - HalftoneHints, - TileWidth, - TileLength, - TileOffsets, - TileByteCounts, - BadFaxLines, - CleanFaxData, - ConsecutiveBadFaxLines, - InkSet, - InkNames, - NumberOfInks, - DotRange, - TargetPrinter, - ExtraSamples, - SampleFormat, - SMinSampleValue, - SMaxSampleValue, - TransferRange, - ClipPath, - XClipPathUnits, - YClipPathUnits, - Indexed, - JPEGTables, - OPIProxy, - ProfileType, - FaxProfile, - CodingMethods, - VersionYear, - ModeNumber, - Decode, - DefaultImageColor, - T82ptions, - JPEGProc, - JPEGInterchangeFormat, - JPEGInterchangeFormatLength, - JPEGRestartInterval, - JPEGLosslessPredictors, - JPEGPointTransforms, - JPEGQTables, - JPEGDCTables, - JPEGACTables, - YCbCrCoefficients, - YCbCrSubsampling, - YCbCrSubsampling, - YCbCrPositioning, - ReferenceBlackWhite, - StripRowCounts, - XMP, - Rating, - RatingPercent, - ImageID, - CFARepeatPatternDim, - CFAPattern2, - BatteryLevel, - Copyright, - MDFileTag, - MDScalePixel, - MDLabName, - MDSampleInfo, - MDPrepDate, - MDPrepTime, - MDFileUnits, - PixelScale, - IntergraphPacketData, - IntergraphRegisters, - IntergraphMatrix, - ModelTiePoint, - SEMInfo, - ModelTransform, - ImageLayer, - FaxRecvParams, - FaxSubaddress, - FaxRecvTime, - ImageSourceData, - XPTitle, - XPComment, - XPAuthor, - XPKeywords, - XPSubject, - GDALMetadata, - GDALNoData - }; - - /// - /// The collection of Exif tags - /// - public static readonly ExifTag[] Exif = - { - ExposureTime, - FNumber, - ExposureProgram, - SpectralSensitivity, - ISOSpeedRatings, - OECF, - Interlace, - TimeZoneOffset, - SelfTimerMode, - SensitivityType, - StandardOutputSensitivity, - RecommendedExposureIndex, - ISOSpeed, - ISOSpeedLatitudeyyy, - ISOSpeedLatitudezzz, - ExifVersion, - DateTimeOriginal, - DateTimeDigitized, - OffsetTime, - OffsetTimeOriginal, - OffsetTimeDigitized, - ComponentsConfiguration, - CompressedBitsPerPixel, - ShutterSpeedValue, - ApertureValue, - BrightnessValue, - ExposureBiasValue, - MaxApertureValue, - SubjectDistance, - MeteringMode, - LightSource, - Flash, - FocalLength, - FlashEnergy2, - SpatialFrequencyResponse2, - Noise, - FocalPlaneXResolution2, - FocalPlaneYResolution2, - FocalPlaneResolutionUnit2, - ImageNumber, - SecurityClassification, - ImageHistory, - SubjectArea, - ExposureIndex2, - TIFFEPStandardID, - SensingMethod2, - MakerNote, - UserComment, - SubsecTime, - SubsecTimeOriginal, - SubsecTimeDigitized, - AmbientTemperature, - Humidity, - Pressure, - WaterDepth, - Acceleration, - CameraElevationAngle, - FlashpixVersion, - ColorSpace, - PixelXDimension, - PixelYDimension, - RelatedSoundFile, - FlashEnergy, - SpatialFrequencyResponse, - FocalPlaneXResolution, - FocalPlaneYResolution, - FocalPlaneResolutionUnit, - SubjectLocation, - ExposureIndex, - SensingMethod, - FileSource, - SceneType, - CFAPattern, - CustomRendered, - ExposureMode, - WhiteBalance, - DigitalZoomRatio, - FocalLengthIn35mmFilm, - SceneCaptureType, - GainControl, - Contrast, - Saturation, - Sharpness, - DeviceSettingDescription, - SubjectDistanceRange, - ImageUniqueID, - OwnerName, - SerialNumber, - LensInfo, - LensMake, - LensModel, - LensSerialNumber - }; - - /// - /// The collection of GPS tags - /// - public static readonly ExifTag[] Gps = - { - GPSVersionID, - GPSLatitudeRef, - GPSLatitude, - GPSLongitudeRef, - GPSLongitude, - GPSAltitudeRef, - GPSAltitude, - GPSTimestamp, - GPSSatellites, - GPSStatus, - GPSMeasureMode, - GPSDOP, - GPSSpeedRef, - GPSSpeed, - GPSTrackRef, - GPSTrack, - GPSImgDirectionRef, - GPSImgDirection, - GPSMapDatum, - GPSDestLatitudeRef, - GPSDestLatitude, - GPSDestLongitudeRef, - GPSDestLongitude, - GPSDestBearingRef, - GPSDestBearing, - GPSDestDistanceRef, - GPSDestDistance, - GPSProcessingMethod, - GPSAreaInformation, - GPSDateStamp, - GPSDifferential - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs deleted file mode 100644 index 05a9f35c9b..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Globalization; -using System.Text; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Represent the value of the EXIF profile. - /// - public sealed class ExifValue : IEquatable, IDeepCloneable - { - /// - /// Initializes a new instance of the class. - /// - /// The tag. - /// The data type. - /// The value. - /// Whether the value is an array. - internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) - { - this.Tag = tag; - this.DataType = dataType; - this.IsArray = isArray && dataType != ExifDataType.Ascii; - this.Value = value; - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another exif value. - /// - /// The other exif value, where the clone should be made from. - /// is null. - private ExifValue(ExifValue other) - { - Guard.NotNull(other, nameof(other)); - - this.DataType = other.DataType; - this.IsArray = other.IsArray; - this.Tag = other.Tag; - - if (!other.IsArray) - { - // All types are value types except for string which is immutable so safe to simply assign. - this.Value = other.Value; - } - else - { - // All array types are value types so Clone() is sufficient here. - var array = (Array)other.Value; - this.Value = array.Clone(); - } - } - - /// - /// Gets the data type of the exif value. - /// - public ExifDataType DataType { get; } - - /// - /// Gets a value indicating whether the value is an array. - /// - public bool IsArray { get; } - - /// - /// Gets the tag of the exif value. - /// - public ExifTag Tag { get; } - - /// - /// Gets the value. - /// - public object Value { get; } - - /// - /// Gets a value indicating whether the EXIF value has a value. - /// - internal bool HasValue - { - get - { - if (this.Value is null) - { - return false; - } - - if (this.DataType == ExifDataType.Ascii) - { - return ((string)this.Value).Length > 0; - } - - return true; - } - } - - /// - /// Gets the length of the EXIF value - /// - internal int Length - { - get - { - if (this.Value is null) - { - return 4; - } - - int size = (int)(GetSize(this.DataType) * this.NumberOfComponents); - - return size < 4 ? 4 : size; - } - } - - /// - /// Gets the number of components. - /// - internal int NumberOfComponents - { - get - { - if (this.DataType == ExifDataType.Ascii) - { - return Encoding.UTF8.GetBytes((string)this.Value).Length; - } - - if (this.IsArray) - { - return ((Array)this.Value).Length; - } - - return 1; - } - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(ExifValue left, ExifValue right) => ReferenceEquals(left, right) || left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(ExifValue left, ExifValue right) => !(left == right); - - /// - public override bool Equals(object obj) => obj is ExifValue other && this.Equals(other); - - /// - public bool Equals(ExifValue other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return - this.Tag == other.Tag - && this.DataType == other.DataType - && object.Equals(this.Value, other.Value); - } - - /// - /// Clones the current value, overwriting the value. - /// - /// The value to overwrite. - /// - public ExifValue WithValue(object value) - { - this.CheckValue(value); - - return new ExifValue(this.Tag, this.DataType, value, this.IsArray); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Tag, this.DataType, this.Value); - } - - /// - public override string ToString() - { - if (this.Value is null) - { - return null; - } - - if (this.DataType == ExifDataType.Ascii) - { - return (string)this.Value; - } - - if (!this.IsArray) - { - return this.ToString(this.Value); - } - - var sb = new StringBuilder(); - foreach (object value in (Array)this.Value) - { - sb.Append(this.ToString(value)); - sb.Append(' '); - } - - return sb.ToString(); - } - - /// - public ExifValue DeepClone() => new ExifValue(this); - - /// - /// Creates a new - /// - /// The tag. - /// The value. - /// - /// The . - /// - /// - /// Thrown if the tag is not supported. - /// - internal static ExifValue Create(ExifTag tag, object value) - { - Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag"); - - switch (tag) - { - case ExifTag.ImageDescription: - case ExifTag.Make: - case ExifTag.Model: - case ExifTag.Software: - case ExifTag.DateTime: - case ExifTag.Artist: - case ExifTag.HostComputer: - case ExifTag.Copyright: - case ExifTag.DocumentName: - case ExifTag.PageName: - case ExifTag.InkNames: - case ExifTag.TargetPrinter: - case ExifTag.ImageID: - case ExifTag.MDLabName: - case ExifTag.MDSampleInfo: - case ExifTag.MDPrepDate: - case ExifTag.MDPrepTime: - case ExifTag.MDFileUnits: - case ExifTag.SEMInfo: - case ExifTag.SpectralSensitivity: - case ExifTag.DateTimeOriginal: - case ExifTag.DateTimeDigitized: - case ExifTag.SubsecTime: - case ExifTag.SubsecTimeOriginal: - case ExifTag.SubsecTimeDigitized: - case ExifTag.FaxSubaddress: - case ExifTag.OffsetTime: - case ExifTag.OffsetTimeOriginal: - case ExifTag.OffsetTimeDigitized: - case ExifTag.SecurityClassification: - case ExifTag.ImageHistory: - case ExifTag.ImageUniqueID: - case ExifTag.OwnerName: - case ExifTag.SerialNumber: - case ExifTag.LensMake: - case ExifTag.LensModel: - case ExifTag.LensSerialNumber: - case ExifTag.GDALMetadata: - case ExifTag.GDALNoData: - case ExifTag.GPSLatitudeRef: - case ExifTag.GPSLongitudeRef: - case ExifTag.GPSSatellites: - case ExifTag.GPSStatus: - case ExifTag.GPSMeasureMode: - case ExifTag.GPSSpeedRef: - case ExifTag.GPSTrackRef: - case ExifTag.GPSImgDirectionRef: - case ExifTag.GPSMapDatum: - case ExifTag.GPSDestLatitudeRef: - case ExifTag.GPSDestLongitudeRef: - case ExifTag.GPSDestBearingRef: - case ExifTag.GPSDestDistanceRef: - case ExifTag.GPSDateStamp: - return new ExifValue(tag, ExifDataType.Ascii, value, true); - - case ExifTag.ClipPath: - case ExifTag.VersionYear: - case ExifTag.XMP: - case ExifTag.CFAPattern2: - case ExifTag.TIFFEPStandardID: - case ExifTag.XPTitle: - case ExifTag.XPComment: - case ExifTag.XPAuthor: - case ExifTag.XPKeywords: - case ExifTag.XPSubject: - case ExifTag.GPSVersionID: - return new ExifValue(tag, ExifDataType.Byte, value, true); - - case ExifTag.FaxProfile: - case ExifTag.ModeNumber: - case ExifTag.GPSAltitudeRef: - return new ExifValue(tag, ExifDataType.Byte, value, false); - - case ExifTag.FreeOffsets: - case ExifTag.FreeByteCounts: - case ExifTag.ColorResponseUnit: - case ExifTag.TileOffsets: - case ExifTag.SMinSampleValue: - case ExifTag.SMaxSampleValue: - case ExifTag.JPEGQTables: - case ExifTag.JPEGDCTables: - case ExifTag.JPEGACTables: - case ExifTag.StripRowCounts: - case ExifTag.IntergraphRegisters: - case ExifTag.TimeZoneOffset: - return new ExifValue(tag, ExifDataType.Long, value, true); - case ExifTag.SubfileType: - case ExifTag.SubIFDOffset: - case ExifTag.GPSIFDOffset: - case ExifTag.T4Options: - case ExifTag.T6Options: - case ExifTag.XClipPathUnits: - case ExifTag.YClipPathUnits: - case ExifTag.ProfileType: - case ExifTag.CodingMethods: - case ExifTag.T82ptions: - case ExifTag.JPEGInterchangeFormat: - case ExifTag.JPEGInterchangeFormatLength: - case ExifTag.MDFileTag: - case ExifTag.StandardOutputSensitivity: - case ExifTag.RecommendedExposureIndex: - case ExifTag.ISOSpeed: - case ExifTag.ISOSpeedLatitudeyyy: - case ExifTag.ISOSpeedLatitudezzz: - case ExifTag.FaxRecvParams: - case ExifTag.FaxRecvTime: - case ExifTag.ImageNumber: - return new ExifValue(tag, ExifDataType.Long, value, false); - - case ExifTag.WhitePoint: - case ExifTag.PrimaryChromaticities: - case ExifTag.YCbCrCoefficients: - case ExifTag.ReferenceBlackWhite: - case ExifTag.PixelScale: - case ExifTag.IntergraphMatrix: - case ExifTag.ModelTiePoint: - case ExifTag.ModelTransform: - case ExifTag.GPSLatitude: - case ExifTag.GPSLongitude: - case ExifTag.GPSTimestamp: - case ExifTag.GPSDestLatitude: - case ExifTag.GPSDestLongitude: - return new ExifValue(tag, ExifDataType.Rational, value, true); - - case ExifTag.XPosition: - case ExifTag.YPosition: - case ExifTag.XResolution: - case ExifTag.YResolution: - case ExifTag.BatteryLevel: - case ExifTag.ExposureTime: - case ExifTag.FNumber: - case ExifTag.MDScalePixel: - case ExifTag.CompressedBitsPerPixel: - case ExifTag.ApertureValue: - case ExifTag.MaxApertureValue: - case ExifTag.SubjectDistance: - case ExifTag.FocalLength: - case ExifTag.FlashEnergy2: - case ExifTag.FocalPlaneXResolution2: - case ExifTag.FocalPlaneYResolution2: - case ExifTag.ExposureIndex2: - case ExifTag.Humidity: - case ExifTag.Pressure: - case ExifTag.Acceleration: - case ExifTag.FlashEnergy: - case ExifTag.FocalPlaneXResolution: - case ExifTag.FocalPlaneYResolution: - case ExifTag.ExposureIndex: - case ExifTag.DigitalZoomRatio: - case ExifTag.LensInfo: - case ExifTag.GPSAltitude: - case ExifTag.GPSDOP: - case ExifTag.GPSSpeed: - case ExifTag.GPSTrack: - case ExifTag.GPSImgDirection: - case ExifTag.GPSDestBearing: - case ExifTag.GPSDestDistance: - return new ExifValue(tag, ExifDataType.Rational, value, false); - - case ExifTag.BitsPerSample: - case ExifTag.MinSampleValue: - case ExifTag.MaxSampleValue: - case ExifTag.GrayResponseCurve: - case ExifTag.ColorMap: - case ExifTag.ExtraSamples: - case ExifTag.PageNumber: - case ExifTag.TransferFunction: - case ExifTag.Predictor: - case ExifTag.HalftoneHints: - case ExifTag.SampleFormat: - case ExifTag.TransferRange: - case ExifTag.DefaultImageColor: - case ExifTag.JPEGLosslessPredictors: - case ExifTag.JPEGPointTransforms: - case ExifTag.YCbCrSubsampling: - case ExifTag.CFARepeatPatternDim: - case ExifTag.IntergraphPacketData: - case ExifTag.ISOSpeedRatings: - case ExifTag.SubjectArea: - case ExifTag.SubjectLocation: - return new ExifValue(tag, ExifDataType.Short, value, true); - - case ExifTag.OldSubfileType: - case ExifTag.Compression: - case ExifTag.PhotometricInterpretation: - case ExifTag.Thresholding: - case ExifTag.CellWidth: - case ExifTag.CellLength: - case ExifTag.FillOrder: - case ExifTag.Orientation: - case ExifTag.SamplesPerPixel: - case ExifTag.PlanarConfiguration: - case ExifTag.GrayResponseUnit: - case ExifTag.ResolutionUnit: - case ExifTag.CleanFaxData: - case ExifTag.InkSet: - case ExifTag.NumberOfInks: - case ExifTag.DotRange: - case ExifTag.Indexed: - case ExifTag.OPIProxy: - case ExifTag.JPEGProc: - case ExifTag.JPEGRestartInterval: - case ExifTag.YCbCrPositioning: - case ExifTag.Rating: - case ExifTag.RatingPercent: - case ExifTag.ExposureProgram: - case ExifTag.Interlace: - case ExifTag.SelfTimerMode: - case ExifTag.SensitivityType: - case ExifTag.MeteringMode: - case ExifTag.LightSource: - case ExifTag.FocalPlaneResolutionUnit2: - case ExifTag.SensingMethod2: - case ExifTag.Flash: - case ExifTag.ColorSpace: - case ExifTag.FocalPlaneResolutionUnit: - case ExifTag.SensingMethod: - case ExifTag.CustomRendered: - case ExifTag.ExposureMode: - case ExifTag.WhiteBalance: - case ExifTag.FocalLengthIn35mmFilm: - case ExifTag.SceneCaptureType: - case ExifTag.GainControl: - case ExifTag.Contrast: - case ExifTag.Saturation: - case ExifTag.Sharpness: - case ExifTag.SubjectDistanceRange: - case ExifTag.GPSDifferential: - return new ExifValue(tag, ExifDataType.Short, value, false); - - case ExifTag.Decode: - return new ExifValue(tag, ExifDataType.SignedRational, value, true); - - case ExifTag.ShutterSpeedValue: - case ExifTag.BrightnessValue: - case ExifTag.ExposureBiasValue: - case ExifTag.AmbientTemperature: - case ExifTag.WaterDepth: - case ExifTag.CameraElevationAngle: - return new ExifValue(tag, ExifDataType.SignedRational, value, false); - - case ExifTag.JPEGTables: - case ExifTag.OECF: - case ExifTag.ExifVersion: - case ExifTag.ComponentsConfiguration: - case ExifTag.MakerNote: - case ExifTag.UserComment: - case ExifTag.FlashpixVersion: - case ExifTag.SpatialFrequencyResponse: - case ExifTag.SpatialFrequencyResponse2: - case ExifTag.Noise: - case ExifTag.CFAPattern: - case ExifTag.DeviceSettingDescription: - case ExifTag.ImageSourceData: - case ExifTag.GPSProcessingMethod: - case ExifTag.GPSAreaInformation: - return new ExifValue(tag, ExifDataType.Undefined, value, true); - - case ExifTag.FileSource: - case ExifTag.SceneType: - return new ExifValue(tag, ExifDataType.Undefined, value, false); - - case ExifTag.StripOffsets: - case ExifTag.TileByteCounts: - case ExifTag.ImageLayer: - return CreateNumber(tag, value, true); - - case ExifTag.ImageWidth: - case ExifTag.ImageLength: - case ExifTag.TileWidth: - case ExifTag.TileLength: - case ExifTag.BadFaxLines: - case ExifTag.ConsecutiveBadFaxLines: - case ExifTag.PixelXDimension: - case ExifTag.PixelYDimension: - return CreateNumber(tag, value, false); - - default: - throw new NotSupportedException(); - } - } - - /// - /// Gets the size in bytes of the given data type. - /// - /// The data type. - /// - /// The . - /// - /// - /// Thrown if the type is unsupported. - /// - internal static uint GetSize(ExifDataType dataType) - { - switch (dataType) - { - case ExifDataType.Ascii: - case ExifDataType.Byte: - case ExifDataType.SignedByte: - case ExifDataType.Undefined: - return 1; - case ExifDataType.Short: - case ExifDataType.SignedShort: - return 2; - case ExifDataType.Long: - case ExifDataType.SignedLong: - case ExifDataType.SingleFloat: - return 4; - case ExifDataType.DoubleFloat: - case ExifDataType.Rational: - case ExifDataType.SignedRational: - return 8; - default: - throw new NotSupportedException(dataType.ToString()); - } - } - - /// - /// Returns an EXIF value with a numeric type for the given tag. - /// - /// The tag. - /// The value. - /// Whether the value is an array. - /// - /// The . - /// - private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray) - { - Type type = value?.GetType(); - if (type?.IsArray == true) - { - type = type.GetElementType(); - } - - if (type is null || type == typeof(ushort)) - { - return new ExifValue(tag, ExifDataType.Short, value, isArray); - } - - if (type == typeof(short)) - { - return new ExifValue(tag, ExifDataType.SignedShort, value, isArray); - } - - if (type == typeof(uint)) - { - return new ExifValue(tag, ExifDataType.Long, value, isArray); - } - - return new ExifValue(tag, ExifDataType.SignedLong, value, isArray); - } - - /// - /// Checks the value type of the given object. - /// - /// The value to check. - /// - /// Thrown if the object type is not supported. - /// - private void CheckValue(object value) - { - if (value is null) - { - return; - } - - Type type = value.GetType(); - - if (this.DataType == ExifDataType.Ascii) - { - Guard.IsTrue(type == typeof(string), nameof(value), "Value should be a string."); - return; - } - - if (type.IsArray) - { - Guard.IsTrue(this.IsArray, nameof(value), "Value should not be an array."); - type = type.GetElementType(); - } - else - { - Guard.IsFalse(this.IsArray, nameof(value), "Value should not be an array."); - } - - switch (this.DataType) - { - case ExifDataType.Byte: - Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.DoubleFloat: - Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Long: - Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Rational: - Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Short: - Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedByte: - Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedLong: - Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedRational: - Guard.IsTrue(type == typeof(SignedRational), nameof(value), $"Value should be a SignedRational{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SignedShort: - Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.SingleFloat: - Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(this.IsArray ? " array." : ".")}"); - break; - case ExifDataType.Undefined: - Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array."); - break; - default: - throw new NotSupportedException(); - } - } - - /// - /// Converts the object value of this instance to its equivalent string representation - /// - /// The value - /// The - private string ToString(object value) - { - if (ExifTagDescriptionAttribute.GetDescription(this.Tag, value) is string description) - { - return description; - } - - switch (this.DataType) - { - case ExifDataType.Ascii: - return (string)value; - case ExifDataType.Byte: - return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); - case ExifDataType.DoubleFloat: - return ((double)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Long: - return ((uint)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Rational: - return ((Rational)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Short: - return ((ushort)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SignedByte: - return ((sbyte)value).ToString("X2", CultureInfo.InvariantCulture); - case ExifDataType.SignedLong: - return ((int)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SignedRational: - return ((Rational)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SignedShort: - return ((short)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.SingleFloat: - return ((float)value).ToString(CultureInfo.InvariantCulture); - case ExifDataType.Undefined: - return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); - default: - throw new NotSupportedException(); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs deleted file mode 100644 index 67c1b2b65e..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Text; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif -{ - /// - /// Contains methods for writing EXIF metadata. - /// - internal sealed class ExifWriter - { - /// - /// Which parts will be written. - /// - private readonly ExifParts allowedParts; - private readonly IList values; - private List dataOffsets; - private readonly List ifdIndexes; - private readonly List exifIndexes; - private readonly List gpsIndexes; - - /// - /// Initializes a new instance of the class. - /// - /// The values. - /// The allowed parts. - public ExifWriter(IList values, ExifParts allowedParts) - { - this.values = values; - this.allowedParts = allowedParts; - this.ifdIndexes = this.GetIndexes(ExifParts.IfdTags, ExifTags.Ifd); - this.exifIndexes = this.GetIndexes(ExifParts.ExifTags, ExifTags.Exif); - this.gpsIndexes = this.GetIndexes(ExifParts.GPSTags, ExifTags.Gps); - } - - /// - /// Returns the EXIF data. - /// - /// - /// The . - /// - public byte[] GetData() - { - uint startIndex = 0; - uint length; - int exifIndex = -1; - int gpsIndex = -1; - - if (this.exifIndexes.Count > 0) - { - exifIndex = this.GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset); - } - - if (this.gpsIndexes.Count > 0) - { - gpsIndex = this.GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset); - } - - uint ifdLength = 2 + this.GetLength(this.ifdIndexes) + 4; - uint exifLength = this.GetLength(this.exifIndexes); - uint gpsLength = this.GetLength(this.gpsIndexes); - - if (exifLength > 0) - { - exifLength += 2; - } - - if (gpsLength > 0) - { - gpsLength += 2; - } - - length = ifdLength + exifLength + gpsLength; - - if (length == 6) - { - return null; - } - - // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total - length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; - - length += 4 + 2; - - var result = new byte[length]; - - int i = 0; - - // the byte order marker for little-endian, followed by the number 42 and a 0 - ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i)); - i += ExifConstants.LittleEndianByteOrderMarker.Length; - - uint ifdOffset = ((uint)i - startIndex) + 4; - uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; - - if (exifLength > 0) - { - this.values[exifIndex] = this.values[exifIndex].WithValue(ifdOffset + ifdLength); - } - - if (gpsLength > 0) - { - this.values[gpsIndex] = this.values[gpsIndex].WithValue(ifdOffset + ifdLength + exifLength); - } - - i = WriteUInt32(ifdOffset, result, i); - i = this.WriteHeaders(this.ifdIndexes, result, i); - i = WriteUInt32(thumbnailOffset, result, i); - i = this.WriteData(startIndex, this.ifdIndexes, result, i); - - if (exifLength > 0) - { - i = this.WriteHeaders(this.exifIndexes, result, i); - i = this.WriteData(startIndex, this.exifIndexes, result, i); - } - - if (gpsLength > 0) - { - i = this.WriteHeaders(this.gpsIndexes, result, i); - i = this.WriteData(startIndex, this.gpsIndexes, result, i); - } - - WriteUInt16(0, result, i); - - return result; - } - - private static unsafe int WriteSingle(float value, Span destination, int offset) - { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), *((int*)&value)); - - return offset + 4; - } - - private static unsafe int WriteDouble(double value, Span destination, int offset) - { - BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), *((long*)&value)); - - return offset + 8; - } - - private static int Write(ReadOnlySpan source, Span destination, int offset) - { - source.CopyTo(destination.Slice(offset, source.Length)); - - return offset + source.Length; - } - - private static int WriteInt16(short value, Span destination, int offset) - { - BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(offset, 2), value); - - return offset + 2; - } - - private static int WriteUInt16(ushort value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(offset, 2), value); - - return offset + 2; - } - - private static int WriteUInt32(uint value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(offset, 4), value); - - return offset + 4; - } - - private static int WriteInt32(int value, Span destination, int offset) - { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); - - return offset + 4; - } - - private int GetIndex(IList indexes, ExifTag tag) - { - foreach (int index in indexes) - { - if (this.values[index].Tag == tag) - { - return index; - } - } - - int newIndex = this.values.Count; - indexes.Add(newIndex); - this.values.Add(ExifValue.Create(tag, null)); - return newIndex; - } - - private List GetIndexes(ExifParts part, ExifTag[] tags) - { - if (((int)this.allowedParts & (int)part) == 0) - { - return new List(); - } - - var result = new List(); - for (int i = 0; i < this.values.Count; i++) - { - ExifValue value = this.values[i]; - - if (!value.HasValue) - { - continue; - } - - int index = Array.IndexOf(tags, value.Tag); - if (index > -1) - { - result.Add(i); - } - } - - return result; - } - - private uint GetLength(IList indexes) - { - uint length = 0; - - foreach (int index in indexes) - { - uint valueLength = (uint)this.values[index].Length; - - if (valueLength > 4) - { - length += 12 + valueLength; - } - else - { - length += 12; - } - } - - return length; - } - - private int WriteArray(ExifValue value, Span destination, int offset) - { - if (value.DataType == ExifDataType.Ascii) - { - return this.WriteValue(ExifDataType.Ascii, value.Value, destination, offset); - } - - int newOffset = offset; - foreach (object obj in (Array)value.Value) - { - newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); - } - - return newOffset; - } - - private int WriteData(uint startIndex, List indexes, Span destination, int offset) - { - if (this.dataOffsets.Count == 0) - { - return offset; - } - - int newOffset = offset; - - int i = 0; - foreach (int index in indexes) - { - ExifValue value = this.values[index]; - if (value.Length > 4) - { - WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); - newOffset = this.WriteValue(value, destination, newOffset); - } - } - - return newOffset; - } - - private int WriteHeaders(List indexes, Span destination, int offset) - { - this.dataOffsets = new List(); - - int newOffset = WriteUInt16((ushort)indexes.Count, destination, offset); - - if (indexes.Count == 0) - { - return newOffset; - } - - foreach (int index in indexes) - { - ExifValue value = this.values[index]; - newOffset = WriteUInt16((ushort)value.Tag, destination, newOffset); - newOffset = WriteUInt16((ushort)value.DataType, destination, newOffset); - newOffset = WriteUInt32((uint)value.NumberOfComponents, destination, newOffset); - - if (value.Length > 4) - { - this.dataOffsets.Add(newOffset); - } - else - { - this.WriteValue(value, destination, newOffset); - } - - newOffset += 4; - } - - return newOffset; - } - - private static void WriteRational(Span destination, in Rational value) - { - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(0, 4), value.Numerator); - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4, 4), value.Denominator); - } - - private static void WriteSignedRational(Span destination, in SignedRational value) - { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(0, 4), value.Numerator); - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); - } - - private int WriteValue(ExifDataType dataType, object value, Span destination, int offset) - { - switch (dataType) - { - case ExifDataType.Ascii: - return Write(Encoding.UTF8.GetBytes((string)value), destination, offset); - case ExifDataType.Byte: - case ExifDataType.Undefined: - destination[offset] = (byte)value; - return offset + 1; - case ExifDataType.DoubleFloat: - return WriteDouble((double)value, destination, offset); - case ExifDataType.Short: - return WriteUInt16((ushort)value, destination, offset); - case ExifDataType.Long: - return WriteUInt32((uint)value, destination, offset); - case ExifDataType.Rational: - WriteRational(destination.Slice(offset, 8), (Rational)value); - return offset + 8; - case ExifDataType.SignedByte: - destination[offset] = unchecked((byte)((sbyte)value)); - return offset + 1; - case ExifDataType.SignedLong: - return WriteInt32((int)value, destination, offset); - case ExifDataType.SignedShort: - return WriteInt16((short)value, destination, offset); - case ExifDataType.SignedRational: - WriteSignedRational(destination.Slice(offset, 8), (SignedRational)value); - return offset + 8; - case ExifDataType.SingleFloat: - return WriteSingle((float)value, destination, offset); - default: - throw new NotImplementedException(); - } - } - - private int WriteValue(ExifValue value, Span destination, int offset) - { - if (value.IsArray && value.DataType != ExifDataType.Ascii) - { - return this.WriteArray(value, destination, offset); - } - - return this.WriteValue(value.DataType, value.Value, destination, offset); - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/README.md b/src/ImageSharp/MetaData/Profiles/Exif/README.md deleted file mode 100644 index b6e27b70c5..0000000000 --- a/src/ImageSharp/MetaData/Profiles/Exif/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Adapted from Magick.NET: - -https://github.com/dlemstra/Magick.NET/tree/784e23b1f5c824fc03d4b95d3387b3efe1ed510b/Magick.NET/Core/Profiles/Exif \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs deleted file mode 100644 index a0ee1d5e50..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ /dev/null @@ -1,902 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Globalization; -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc -{ - /// - /// Provides methods to read ICC data types - /// - internal sealed partial class IccDataReader - { - /// - /// Reads a tag data entry - /// - /// The table entry with reading information - /// the tag data entry - public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) - { - this.currentIndex = (int)info.Offset; - IccTypeSignature type = this.ReadTagDataEntryHeader(); - - switch (type) - { - case IccTypeSignature.Chromaticity: - return this.ReadChromaticityTagDataEntry(); - case IccTypeSignature.ColorantOrder: - return this.ReadColorantOrderTagDataEntry(); - case IccTypeSignature.ColorantTable: - return this.ReadColorantTableTagDataEntry(); - case IccTypeSignature.Curve: - return this.ReadCurveTagDataEntry(); - case IccTypeSignature.Data: - return this.ReadDataTagDataEntry(info.DataSize); - case IccTypeSignature.DateTime: - return this.ReadDateTimeTagDataEntry(); - case IccTypeSignature.Lut16: - return this.ReadLut16TagDataEntry(); - case IccTypeSignature.Lut8: - return this.ReadLut8TagDataEntry(); - case IccTypeSignature.LutAToB: - return this.ReadLutAtoBTagDataEntry(); - case IccTypeSignature.LutBToA: - return this.ReadLutBtoATagDataEntry(); - case IccTypeSignature.Measurement: - return this.ReadMeasurementTagDataEntry(); - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.MultiProcessElements: - return this.ReadMultiProcessElementsTagDataEntry(); - case IccTypeSignature.NamedColor2: - return this.ReadNamedColor2TagDataEntry(); - case IccTypeSignature.ParametricCurve: - return this.ReadParametricCurveTagDataEntry(); - case IccTypeSignature.ProfileSequenceDesc: - return this.ReadProfileSequenceDescTagDataEntry(); - case IccTypeSignature.ProfileSequenceIdentifier: - return this.ReadProfileSequenceIdentifierTagDataEntry(); - case IccTypeSignature.ResponseCurveSet16: - return this.ReadResponseCurveSet16TagDataEntry(); - case IccTypeSignature.S15Fixed16Array: - return this.ReadFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.Signature: - return this.ReadSignatureTagDataEntry(); - case IccTypeSignature.Text: - return this.ReadTextTagDataEntry(info.DataSize); - case IccTypeSignature.U16Fixed16Array: - return this.ReadUFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt16Array: - return this.ReadUInt16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt32Array: - return this.ReadUInt32ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt64Array: - return this.ReadUInt64ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt8Array: - return this.ReadUInt8ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.ViewingConditions: - return this.ReadViewingConditionsTagDataEntry(); - case IccTypeSignature.Xyz: - return this.ReadXyzTagDataEntry(info.DataSize); - - // V2 Types: - case IccTypeSignature.TextDescription: - return this.ReadTextDescriptionTagDataEntry(); - case IccTypeSignature.CrdInfo: - return this.ReadCrdInfoTagDataEntry(); - case IccTypeSignature.Screening: - return this.ReadScreeningTagDataEntry(); - case IccTypeSignature.UcrBg: - return this.ReadUcrBgTagDataEntry(info.DataSize); - - // Unsupported or unknown - case IccTypeSignature.DeviceSettings: - case IccTypeSignature.NamedColor: - case IccTypeSignature.Unknown: - default: - return this.ReadUnknownTagDataEntry(info.DataSize); - } - } - - /// - /// Reads the header of a - /// - /// The read signature - public IccTypeSignature ReadTagDataEntryHeader() - { - var type = (IccTypeSignature)this.ReadUInt32(); - this.AddIndex(4); // 4 bytes are not used - return type; - } - - /// - /// Reads the header of a and checks if it's the expected value - /// - /// expected value to check against - public void ReadCheckTagDataEntryHeader(IccTypeSignature expected) - { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - if (expected != (IccTypeSignature)uint.MaxValue && type != expected) - { - throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}"); - } - } - - /// - /// Reads a with an unknown - /// - /// The size of the entry in bytes - /// The read entry - public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) - { - int count = (int)size - 8; // 8 is the tag header size - return new IccUnknownTagDataEntry(this.ReadBytes(count)); - } - - /// - /// Reads a - /// - /// The read entry - public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry() - { - ushort channelCount = this.ReadUInt16(); - var colorant = (IccColorantEncoding)this.ReadUInt16(); - - if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown) - { - // The type is known and so are the values (they are constant) - // channelCount should always be 3 but it doesn't really matter if it's not - return new IccChromaticityTagDataEntry(colorant); - } - else - { - // The type is not know, so the values need be read - var values = new double[channelCount][]; - for (int i = 0; i < channelCount; i++) - { - values[i] = new double[] { this.ReadUFix16(), this.ReadUFix16() }; - } - - return new IccChromaticityTagDataEntry(values); - } - } - - /// - /// Reads a - /// - /// The read entry - public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() - { - uint colorantCount = this.ReadUInt32(); - byte[] number = this.ReadBytes((int)colorantCount); - return new IccColorantOrderTagDataEntry(number); - } - - /// - /// Reads a - /// - /// The read entry - public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry() - { - uint colorantCount = this.ReadUInt32(); - var cdata = new IccColorantTableEntry[colorantCount]; - for (int i = 0; i < colorantCount; i++) - { - cdata[i] = this.ReadColorantTableEntry(); - } - - return new IccColorantTableTagDataEntry(cdata); - } - - /// - /// Reads a - /// - /// The read entry - public IccCurveTagDataEntry ReadCurveTagDataEntry() - { - uint pointCount = this.ReadUInt32(); - - if (pointCount == 0) - { - return new IccCurveTagDataEntry(); - } - - if (pointCount == 1) - { - return new IccCurveTagDataEntry(this.ReadUFix8()); - } - - var cdata = new float[pointCount]; - for (int i = 0; i < pointCount; i++) - { - cdata[i] = this.ReadUInt16() / 65535f; - } - - return new IccCurveTagDataEntry(cdata); - - // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccDataTagDataEntry ReadDataTagDataEntry(uint size) - { - this.AddIndex(3); // first 3 bytes are zero - byte b = this.data[this.AddIndex(1)]; - - // last bit of 4th byte is either 0 = ASCII or 1 = binary - bool ascii = this.GetBit(b, 7); - int length = (int)size - 12; - byte[] cdata = this.ReadBytes(length); - - return new IccDataTagDataEntry(cdata, ascii); - } - - /// - /// Reads a - /// - /// The read entry - public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() - { - return new IccDateTimeTagDataEntry(this.ReadDateTime()); - } - - /// - /// Reads a - /// - /// The read entry - public IccLut16TagDataEntry ReadLut16TagDataEntry() - { - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - byte clutPointCount = this.data[this.AddIndex(1)]; - this.AddIndex(1); // 1 byte reserved - - float[,] matrix = this.ReadMatrix(3, 3, false); - - ushort inTableCount = this.ReadUInt16(); - ushort outTableCount = this.ReadUInt16(); - - // Input LUT - var inValues = new IccLut[inChCount]; - var gridPointCount = new byte[inChCount]; - for (int i = 0; i < inChCount; i++) - { - inValues[i] = this.ReadLut16(inTableCount); - gridPointCount[i] = clutPointCount; - } - - // CLUT - IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount); - - // Output LUT - var outValues = new IccLut[outChCount]; - for (int i = 0; i < outChCount; i++) - { - outValues[i] = this.ReadLut16(outTableCount); - } - - return new IccLut16TagDataEntry(matrix, inValues, clut, outValues); - } - - /// - /// Reads a - /// - /// The read entry - public IccLut8TagDataEntry ReadLut8TagDataEntry() - { - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - byte clutPointCount = this.data[this.AddIndex(1)]; - this.AddIndex(1); // 1 byte reserved - - float[,] matrix = this.ReadMatrix(3, 3, false); - - // Input LUT - var inValues = new IccLut[inChCount]; - var gridPointCount = new byte[inChCount]; - for (int i = 0; i < inChCount; i++) - { - inValues[i] = this.ReadLut8(); - gridPointCount[i] = clutPointCount; - } - - // CLUT - IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount); - - // Output LUT - var outValues = new IccLut[outChCount]; - for (int i = 0; i < outChCount; i++) - { - outValues[i] = this.ReadLut8(); - } - - return new IccLut8TagDataEntry(matrix, inValues, clut, outValues); - } - - /// - /// Reads a - /// - /// The read entry - public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - this.AddIndex(2); // 2 bytes reserved - - uint bCurveOffset = this.ReadUInt32(); - uint matrixOffset = this.ReadUInt32(); - uint mCurveOffset = this.ReadUInt32(); - uint clutOffset = this.ReadUInt32(); - uint aCurveOffset = this.ReadUInt32(); - - IccTagDataEntry[] bCurve = null; - IccTagDataEntry[] mCurve = null; - IccTagDataEntry[] aCurve = null; - IccClut clut = null; - float[,] matrix3x3 = null; - float[] matrix3x1 = null; - - if (bCurveOffset != 0) - { - this.currentIndex = (int)bCurveOffset + start; - bCurve = this.ReadCurves(outChCount); - } - - if (mCurveOffset != 0) - { - this.currentIndex = (int)mCurveOffset + start; - mCurve = this.ReadCurves(outChCount); - } - - if (aCurveOffset != 0) - { - this.currentIndex = (int)aCurveOffset + start; - aCurve = this.ReadCurves(inChCount); - } - - if (clutOffset != 0) - { - this.currentIndex = (int)clutOffset + start; - clut = this.ReadClut(inChCount, outChCount, false); - } - - if (matrixOffset != 0) - { - this.currentIndex = (int)matrixOffset + start; - matrix3x3 = this.ReadMatrix(3, 3, false); - matrix3x1 = this.ReadMatrix(3, false); - } - - return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); - } - - /// - /// Reads a - /// - /// The read entry - public IccLutBToATagDataEntry ReadLutBtoATagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - this.AddIndex(2); // 2 bytes reserved - - uint bCurveOffset = this.ReadUInt32(); - uint matrixOffset = this.ReadUInt32(); - uint mCurveOffset = this.ReadUInt32(); - uint clutOffset = this.ReadUInt32(); - uint aCurveOffset = this.ReadUInt32(); - - IccTagDataEntry[] bCurve = null; - IccTagDataEntry[] mCurve = null; - IccTagDataEntry[] aCurve = null; - IccClut clut = null; - float[,] matrix3x3 = null; - float[] matrix3x1 = null; - - if (bCurveOffset != 0) - { - this.currentIndex = (int)bCurveOffset + start; - bCurve = this.ReadCurves(inChCount); - } - - if (mCurveOffset != 0) - { - this.currentIndex = (int)mCurveOffset + start; - mCurve = this.ReadCurves(inChCount); - } - - if (aCurveOffset != 0) - { - this.currentIndex = (int)aCurveOffset + start; - aCurve = this.ReadCurves(outChCount); - } - - if (clutOffset != 0) - { - this.currentIndex = (int)clutOffset + start; - clut = this.ReadClut(inChCount, outChCount, false); - } - - if (matrixOffset != 0) - { - this.currentIndex = (int)matrixOffset + start; - matrix3x3 = this.ReadMatrix(3, 3, false); - matrix3x1 = this.ReadMatrix(3, false); - } - - return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); - } - - /// - /// Reads a - /// - /// The read entry - public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() - { - return new IccMeasurementTagDataEntry( - observer: (IccStandardObserver)this.ReadUInt32(), - xyzBacking: this.ReadXyzNumber(), - geometry: (IccMeasurementGeometry)this.ReadUInt32(), - flare: this.ReadUFix16(), - illuminant: (IccStandardIlluminant)this.ReadUInt32()); - } - - /// - /// Reads a - /// - /// The read entry - public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - uint recordCount = this.ReadUInt32(); - - this.ReadUInt32(); // Record size (always 12) - var text = new IccLocalizedString[recordCount]; - - var culture = new CultureInfo[recordCount]; - var length = new uint[recordCount]; - var offset = new uint[recordCount]; - - for (int i = 0; i < recordCount; i++) - { - string languageCode = this.ReadAsciiString(2); - string countryCode = this.ReadAsciiString(2); - - culture[i] = ReadCulture(languageCode, countryCode); - length[i] = this.ReadUInt32(); - offset[i] = this.ReadUInt32(); - } - - for (int i = 0; i < recordCount; i++) - { - this.currentIndex = (int)(start + offset[i]); - text[i] = new IccLocalizedString(culture[i], this.ReadUnicodeString((int)length[i])); - } - - return new IccMultiLocalizedUnicodeTagDataEntry(text); - - CultureInfo ReadCulture(string language, string country) - { - if (string.IsNullOrWhiteSpace(language)) - { - return CultureInfo.InvariantCulture; - } - else if (string.IsNullOrWhiteSpace(country)) - { - try - { - return new CultureInfo(language); - } - catch (CultureNotFoundException) - { - return CultureInfo.InvariantCulture; - } - } - else - { - try - { - return new CultureInfo($"{language}-{country}"); - } - catch (CultureNotFoundException) - { - return ReadCulture(language, null); - } - } - } - } - - /// - /// Reads a - /// - /// The read entry - public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry() - { - int start = this.currentIndex - 8; - - this.ReadUInt16(); - this.ReadUInt16(); - uint elementCount = this.ReadUInt32(); - - var positionTable = new IccPositionNumber[elementCount]; - for (int i = 0; i < elementCount; i++) - { - positionTable[i] = this.ReadPositionNumber(); - } - - var elements = new IccMultiProcessElement[elementCount]; - for (int i = 0; i < elementCount; i++) - { - this.currentIndex = (int)positionTable[i].Offset + start; - elements[i] = this.ReadMultiProcessElement(); - } - - return new IccMultiProcessElementsTagDataEntry(elements); - } - - /// - /// Reads a - /// - /// The read entry - public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry() - { - int vendorFlag = this.ReadInt32(); - uint colorCount = this.ReadUInt32(); - uint coordCount = this.ReadUInt32(); - string prefix = this.ReadAsciiString(32); - string suffix = this.ReadAsciiString(32); - - var colors = new IccNamedColor[colorCount]; - for (int i = 0; i < colorCount; i++) - { - colors[i] = this.ReadNamedColor(coordCount); - } - - return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors); - } - - /// - /// Reads a - /// - /// The read entry - public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() - { - return new IccParametricCurveTagDataEntry(this.ReadParametricCurve()); - } - - /// - /// Reads a - /// - /// The read entry - public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry() - { - uint count = this.ReadUInt32(); - var description = new IccProfileDescription[count]; - for (int i = 0; i < count; i++) - { - description[i] = this.ReadProfileDescription(); - } - - return new IccProfileSequenceDescTagDataEntry(description); - } - - /// - /// Reads a - /// - /// The read entry - public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - uint count = this.ReadUInt32(); - var table = new IccPositionNumber[count]; - for (int i = 0; i < count; i++) - { - table[i] = this.ReadPositionNumber(); - } - - var entries = new IccProfileSequenceIdentifier[count]; - for (int i = 0; i < count; i++) - { - this.currentIndex = (int)(start + table[i].Offset); - IccProfileId id = this.ReadProfileId(); - this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode); - IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry(); - entries[i] = new IccProfileSequenceIdentifier(id, description.Texts); - } - - return new IccProfileSequenceIdentifierTagDataEntry(entries); - } - - /// - /// Reads a - /// - /// The read entry - public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - ushort channelCount = this.ReadUInt16(); - ushort measurementCount = this.ReadUInt16(); - - var offset = new uint[measurementCount]; - for (int i = 0; i < measurementCount; i++) - { - offset[i] = this.ReadUInt32(); - } - - var curves = new IccResponseCurve[measurementCount]; - for (int i = 0; i < measurementCount; i++) - { - this.currentIndex = (int)(start + offset[i]); - curves[i] = this.ReadResponseCurve(channelCount); - } - - return new IccResponseCurveSet16TagDataEntry(curves); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - var arrayData = new float[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadFix16() / 256f; - } - - return new IccFix16ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The read entry - public IccSignatureTagDataEntry ReadSignatureTagDataEntry() - { - return new IccSignatureTagDataEntry(this.ReadAsciiString(4)); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccTextTagDataEntry ReadTextTagDataEntry(uint size) - { - return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - var arrayData = new float[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUFix16(); - } - - return new IccUFix16ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 2; - var arrayData = new ushort[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt16(); - } - - return new IccUInt16ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - var arrayData = new uint[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt32(); - } - - return new IccUInt32ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 8; - var arrayData = new ulong[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt64(); - } - - return new IccUInt64ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size) - { - int count = (int)size - 8; // 8 is the tag header size - byte[] adata = this.ReadBytes(count); - - return new IccUInt8ArrayTagDataEntry(adata); - } - - /// - /// Reads a - /// - /// The read entry - public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() - { - return new IccViewingConditionsTagDataEntry( - illuminantXyz: this.ReadXyzNumber(), - surroundXyz: this.ReadXyzNumber(), - illuminant: (IccStandardIlluminant)this.ReadUInt32()); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size) - { - uint count = (size - 8) / 12; - var arrayData = new Vector3[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadXyzNumber(); - } - - return new IccXyzTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The read entry - public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry() - { - string unicodeValue, scriptcodeValue; - string asciiValue = unicodeValue = scriptcodeValue = null; - - int asciiCount = (int)this.ReadUInt32(); - if (asciiCount > 0) - { - asciiValue = this.ReadAsciiString(asciiCount - 1); - this.AddIndex(1); // Null terminator - } - - uint unicodeLangCode = this.ReadUInt32(); - int unicodeCount = (int)this.ReadUInt32(); - if (unicodeCount > 0) - { - unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2); - this.AddIndex(2); // Null terminator - } - - ushort scriptcodeCode = this.ReadUInt16(); - int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67); - if (scriptcodeCount > 0) - { - scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1); - this.AddIndex(1); // Null terminator - } - - return new IccTextDescriptionTagDataEntry( - asciiValue, - unicodeValue, - scriptcodeValue, - unicodeLangCode, - scriptcodeCode); - } - - /// - /// Reads a - /// - /// The read entry - public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry() - { - uint productNameCount = this.ReadUInt32(); - string productName = this.ReadAsciiString((int)productNameCount); - - uint crd0Count = this.ReadUInt32(); - string crd0Name = this.ReadAsciiString((int)crd0Count); - - uint crd1Count = this.ReadUInt32(); - string crd1Name = this.ReadAsciiString((int)crd1Count); - - uint crd2Count = this.ReadUInt32(); - string crd2Name = this.ReadAsciiString((int)crd2Count); - - uint crd3Count = this.ReadUInt32(); - string crd3Name = this.ReadAsciiString((int)crd3Count); - - return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name); - } - - /// - /// Reads a - /// - /// The read entry - public IccScreeningTagDataEntry ReadScreeningTagDataEntry() - { - var flags = (IccScreeningFlag)this.ReadInt32(); - uint channelCount = this.ReadUInt32(); - var channels = new IccScreeningChannel[channelCount]; - for (int i = 0; i < channels.Length; i++) - { - channels[i] = this.ReadScreeningChannel(); - } - - return new IccScreeningTagDataEntry(flags, channels); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) - { - uint ucrCount = this.ReadUInt32(); - var ucrCurve = new ushort[ucrCount]; - for (int i = 0; i < ucrCurve.Length; i++) - { - ucrCurve[i] = this.ReadUInt16(); - } - - uint bgCount = this.ReadUInt32(); - var bgCurve = new ushort[bgCount]; - for (int i = 0; i < bgCurve.Length; i++) - { - bgCurve[i] = this.ReadUInt16(); - } - - // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount) - uint dataSize = ((ucrCount + bgCount) * 2) + 8; - int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size - string description = this.ReadAsciiString(descriptionLength); - - return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description); - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs deleted file mode 100644 index b0bd377cb3..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc -{ - /// - /// Provides methods to write ICC data types - /// - internal sealed partial class IccDataWriter - { - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(Matrix4x4 value, bool isSingle) - { - int count = 0; - - if (isSingle) - { - count += this.WriteSingle(value.M11); - count += this.WriteSingle(value.M21); - count += this.WriteSingle(value.M31); - - count += this.WriteSingle(value.M12); - count += this.WriteSingle(value.M22); - count += this.WriteSingle(value.M32); - - count += this.WriteSingle(value.M13); - count += this.WriteSingle(value.M23); - count += this.WriteSingle(value.M33); - } - else - { - count += this.WriteFix16(value.M11); - count += this.WriteFix16(value.M21); - count += this.WriteFix16(value.M31); - - count += this.WriteFix16(value.M12); - count += this.WriteFix16(value.M22); - count += this.WriteFix16(value.M32); - - count += this.WriteFix16(value.M13); - count += this.WriteFix16(value.M23); - count += this.WriteFix16(value.M33); - } - - return count; - } - - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(in DenseMatrix value, bool isSingle) - { - int count = 0; - for (int y = 0; y < value.Rows; y++) - { - for (int x = 0; x < value.Columns; x++) - { - if (isSingle) - { - count += this.WriteSingle(value[x, y]); - } - else - { - count += this.WriteFix16(value[x, y]); - } - } - } - - return count; - } - - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(float[,] value, bool isSingle) - { - int count = 0; - for (int y = 0; y < value.GetLength(1); y++) - { - for (int x = 0; x < value.GetLength(0); x++) - { - if (isSingle) - { - count += this.WriteSingle(value[x, y]); - } - else - { - count += this.WriteFix16(value[x, y]); - } - } - } - - return count; - } - - /// - /// Writes a one dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(Vector3 value, bool isSingle) - { - int count = 0; - if (isSingle) - { - count += this.WriteSingle(value.X); - count += this.WriteSingle(value.Y); - count += this.WriteSingle(value.Z); - } - else - { - count += this.WriteFix16(value.X); - count += this.WriteFix16(value.Y); - count += this.WriteFix16(value.Z); - } - - return count; - } - - /// - /// Writes a one dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(float[] value, bool isSingle) - { - int count = 0; - for (int i = 0; i < value.Length; i++) - { - if (isSingle) - { - count += this.WriteSingle(value[i]); - } - else - { - count += this.WriteFix16(value[i]); - } - } - - return count; - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs deleted file mode 100644 index 0d8683397a..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc -{ - /// - /// A matrix element to process data - /// - internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable - { - /// - /// Initializes a new instance of the class. - /// - /// Two dimensional matrix with size of Input-Channels x Output-Channels - /// One dimensional matrix with size of Output-Channels x 1 - public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) - : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) - { - Guard.NotNull(matrixIxO, nameof(matrixIxO)); - Guard.NotNull(matrixOx1, nameof(matrixOx1)); - - bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; - Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); - - this.MatrixIxO = matrixIxO; - this.MatrixOx1 = matrixOx1; - } - - /// - /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels - /// - public DenseMatrix MatrixIxO { get; } - - /// - /// Gets the one dimensional matrix with size of Output-Channels x 1 - /// - public float[] MatrixOx1 { get; } - - /// - public override bool Equals(IccMultiProcessElement other) - { - if (base.Equals(other) && other is IccMatrixProcessElement element) - { - return this.EqualsMatrix(element) - && this.MatrixOx1.AsSpan().SequenceEqual(element.MatrixOx1); - } - - return false; - } - - /// - public bool Equals(IccMatrixProcessElement other) - { - return this.Equals((IccMultiProcessElement)other); - } - - private bool EqualsMatrix(IccMatrixProcessElement element) - { - return this.MatrixIxO.Equals(element.MatrixIxO); - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs deleted file mode 100644 index 88e3c4cae2..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Linq; -using System.Numerics; - -// TODO: Review the use of base IccTagDataEntry comparison. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc -{ - /// - /// This structure represents a color transform. - /// - internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable - { - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - public IccLutAToBTagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA) - : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - /// Tag Signature - public IccLutAToBTagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA, - IccProfileTag tagSignature) - : base(IccTypeSignature.LutAToB, tagSignature) - { - this.VerifyMatrix(matrix3x3, matrix3x1); - this.VerifyCurve(curveA, nameof(curveA)); - this.VerifyCurve(curveB, nameof(curveB)); - this.VerifyCurve(curveM, nameof(curveM)); - - this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); - this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); - this.CurveA = curveA; - this.CurveB = curveB; - this.CurveM = curveM; - this.ClutValues = clutValues; - - if (this.IsAClutMMatrixB()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - - this.InputChannelCount = curveA.Length; - this.OutputChannelCount = 3; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsMMatrixB()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - - this.InputChannelCount = this.OutputChannelCount = 3; - } - else if (this.IsAClutB()) - { - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - - this.InputChannelCount = curveA.Length; - this.OutputChannelCount = curveB.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsB()) - { - this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; - } - else - { - throw new ArgumentException("Invalid combination of values given"); - } - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the two dimensional conversion matrix (3x3) - /// - public Matrix4x4? Matrix3x3 { get; } - - /// - /// Gets the one dimensional conversion matrix (3x1) - /// - public Vector3? Matrix3x1 { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the B Curve - /// - public IccTagDataEntry[] CurveB { get; } - - /// - /// Gets the M Curve - /// - public IccTagDataEntry[] CurveM { get; } - - /// - /// Gets the A Curve - /// - public IccTagDataEntry[] CurveA { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLutAToBTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLutAToBTagDataEntry other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Matrix3x3.Equals(other.Matrix3x3) - && this.Matrix3x1.Equals(other.Matrix3x1) - && this.ClutValues.Equals(other.ClutValues) - && EqualsCurve(this.CurveB, other.CurveB) - && EqualsCurve(this.CurveM, other.CurveM) - && EqualsCurve(this.CurveA, other.CurveA); - } - - /// - public override bool Equals(object obj) => obj is IccLutAToBTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - HashCode hashCode = default; - - hashCode.Add(this.Signature); - hashCode.Add(this.InputChannelCount); - hashCode.Add(this.OutputChannelCount); - hashCode.Add(this.Matrix3x3); - hashCode.Add(this.Matrix3x1); - hashCode.Add(this.ClutValues); - hashCode.Add(this.CurveB); - hashCode.Add(this.CurveM); - hashCode.Add(this.CurveA); - - return hashCode.ToHashCode(); - } - - private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) - { - bool thisNull = thisCurves is null; - bool entryNull = entryCurves is null; - - if (thisNull && entryNull) - { - return true; - } - - if (entryNull) - { - return false; - } - - return thisCurves.SequenceEqual(entryCurves); - } - - private bool IsAClutMMatrixB() - { - return this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null - && this.ClutValues != null - && this.CurveA != null; - } - - private bool IsMMatrixB() - { - return this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null; - } - - private bool IsAClutB() - { - return this.CurveB != null - && this.ClutValues != null - && this.CurveA != null; - } - - private bool IsB() => this.CurveB != null; - - private void VerifyCurve(IccTagDataEntry[] curves, string name) - { - if (curves != null) - { - bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); - Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); - } - } - - private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) - { - if (matrix3x1 != null) - { - Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); - } - - if (matrix3x3 != null) - { - bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); - } - } - - private Vector3? CreateMatrix3x1(float[] matrix) - { - if (matrix is null) - { - return null; - } - - return new Vector3(matrix[0], matrix[1], matrix[2]); - } - - private Matrix4x4? CreateMatrix3x3(float[,] matrix) - { - if (matrix is null) - { - return null; - } - - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs deleted file mode 100644 index f8bf3f0423..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Linq; -using System.Numerics; - -// TODO: Review the use of base IccTagDataEntry comparison. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc -{ - /// - /// This structure represents a color transform. - /// - internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable - { - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - public IccLutBToATagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA) - : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - /// Tag Signature - public IccLutBToATagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA, - IccProfileTag tagSignature) - : base(IccTypeSignature.LutBToA, tagSignature) - { - this.VerifyMatrix(matrix3x3, matrix3x1); - this.VerifyCurve(curveA, nameof(curveA)); - this.VerifyCurve(curveB, nameof(curveB)); - this.VerifyCurve(curveM, nameof(curveM)); - - this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); - this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); - this.CurveA = curveA; - this.CurveB = curveB; - this.CurveM = curveM; - this.ClutValues = clutValues; - - if (this.IsBMatrixMClutA()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - - this.InputChannelCount = 3; - this.OutputChannelCount = curveA.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsBMatrixM()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - - this.InputChannelCount = this.OutputChannelCount = 3; - } - else if (this.IsBClutA()) - { - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - - this.InputChannelCount = curveB.Length; - this.OutputChannelCount = curveA.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsB()) - { - this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; - } - else - { - throw new ArgumentException("Invalid combination of values given"); - } - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the two dimensional conversion matrix (3x3) - /// - public Matrix4x4? Matrix3x3 { get; } - - /// - /// Gets the one dimensional conversion matrix (3x1) - /// - public Vector3? Matrix3x1 { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the B Curve - /// - public IccTagDataEntry[] CurveB { get; } - - /// - /// Gets the M Curve - /// - public IccTagDataEntry[] CurveM { get; } - - /// - /// Gets the A Curve - /// - public IccTagDataEntry[] CurveA { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLutBToATagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLutBToATagDataEntry other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Matrix3x3.Equals(other.Matrix3x3) - && this.Matrix3x1.Equals(other.Matrix3x1) - && this.ClutValues.Equals(other.ClutValues) - && this.EqualsCurve(this.CurveB, other.CurveB) - && this.EqualsCurve(this.CurveM, other.CurveM) - && this.EqualsCurve(this.CurveA, other.CurveA); - } - - /// - public override bool Equals(object obj) => obj is IccLutBToATagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - HashCode hashCode = default; - hashCode.Add(this.Signature); - hashCode.Add(this.InputChannelCount); - hashCode.Add(this.OutputChannelCount); - hashCode.Add(this.Matrix3x3); - hashCode.Add(this.Matrix3x1); - hashCode.Add(this.ClutValues); - hashCode.Add(this.CurveB); - hashCode.Add(this.CurveM); - hashCode.Add(this.CurveA); - - return hashCode.ToHashCode(); - } - - private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) - { - bool thisNull = thisCurves is null; - bool entryNull = entryCurves is null; - - if (thisNull && entryNull) - { - return true; - } - - if (entryNull) - { - return false; - } - - return thisCurves.SequenceEqual(entryCurves); - } - - private bool IsBMatrixMClutA() - { - return this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null - && this.ClutValues != null - && this.CurveA != null; - } - - private bool IsBMatrixM() - { - return this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null; - } - - private bool IsBClutA() - { - return this.CurveB != null - && this.ClutValues != null - && this.CurveA != null; - } - - private bool IsB() => this.CurveB != null; - - private void VerifyCurve(IccTagDataEntry[] curves, string name) - { - if (curves != null) - { - bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); - Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); - } - } - - private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) - { - if (matrix3x1 != null) - { - Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); - } - - if (matrix3x3 != null) - { - bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); - } - } - - private Vector3? CreateMatrix3x1(float[] matrix) - { - if (matrix is null) - { - return null; - } - - return new Vector3(matrix[0], matrix[1], matrix[2]); - } - - private Matrix4x4? CreateMatrix3x3(float[,] matrix) - { - if (matrix is null) - { - return null; - } - - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); - } - } -} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs deleted file mode 100644 index 505b73a89f..0000000000 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc -{ - /// - /// The XYZType contains an array of XYZ values. - /// - internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable - { - /// - /// Initializes a new instance of the class. - /// - /// The XYZ numbers. - public IccXyzTagDataEntry(Vector3[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The XYZ numbers - /// Tag Signature - public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.Xyz, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the XYZ numbers. - /// - public Vector3[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry other) - { - if (base.Equals(other) && other is IccXyzTagDataEntry entry) - { - return this.Data.AsSpan().SequenceEqual(entry.Data); - } - - return false; - } - - /// - public bool Equals(IccXyzTagDataEntry other) - { - return this.Equals((IccTagDataEntry)other); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/FrameDecodingMode.cs b/src/ImageSharp/Metadata/FrameDecodingMode.cs similarity index 100% rename from src/ImageSharp/MetaData/FrameDecodingMode.cs rename to src/ImageSharp/Metadata/FrameDecodingMode.cs diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs similarity index 100% rename from src/ImageSharp/MetaData/ImageFrameMetaData.cs rename to src/ImageSharp/Metadata/ImageFrameMetadata.cs diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs new file mode 100644 index 0000000000..716e89e68d --- /dev/null +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; + +namespace SixLabors.ImageSharp.Metadata +{ + /// + /// Encapsulates the metadata of an image. + /// + public sealed class ImageMetadata : IDeepCloneable + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 . + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 . + /// + public const double DefaultVerticalResolution = 96; + + /// + /// The default pixel resolution units. + /// The default value is . + /// + public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; + + private readonly Dictionary formatMetadata = new Dictionary(); + private double horizontalResolution; + private double verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + internal ImageMetadata() + { + this.horizontalResolution = DefaultHorizontalResolution; + this.verticalResolution = DefaultVerticalResolution; + this.ResolutionUnits = DefaultPixelResolutionUnits; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + private ImageMetadata(ImageMetadata other) + { + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.ResolutionUnits = other.ResolutionUnits; + + foreach (KeyValuePair meta in other.formatMetadata) + { + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); + } + + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); + this.IptcProfile = other.IptcProfile?.DeepClone(); + } + + /// + /// Gets or sets the resolution of the image in x- direction. + /// It is defined as the number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution + { + get => this.horizontalResolution; + + set + { + if (value > 0) + { + this.horizontalResolution = value; + } + } + } + + /// + /// Gets or sets the resolution of the image in y- direction. + /// It is defined as the number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution + { + get => this.verticalResolution; + + set + { + if (value > 0) + { + this.verticalResolution = value; + } + } + } + + /// + /// Gets or sets unit of measure used when reporting resolution. + /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// 01 : Pixels per inch (2.54 cm) + /// 02 : Pixels per centimeter + /// 03 : Pixels per meter + /// + public PixelResolutionUnit ResolutionUnits { get; set; } + + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Gets or sets the list of ICC profiles. + /// + public IccProfile IccProfile { get; set; } + + /// + /// Gets or sets the iptc profile. + /// + public IptcProfile IptcProfile { get; set; } + + /// + /// Gets the metadata value associated with the specified key. + /// + /// The type of metadata. + /// The key of the value to get. + /// + /// The . + /// + public TFormatMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class, IDeepCloneable + { + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) + { + return (TFormatMetadata)meta; + } + + TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); + this.formatMetadata[key] = newMeta; + return newMeta; + } + + /// + public ImageMetadata DeepClone() => new ImageMetadata(this); + + /// + /// Synchronizes the profiles with the current metadata. + /// + internal void SyncProfiles() => this.ExifProfile?.Sync(this); + } +} diff --git a/src/ImageSharp/MetaData/PixelResolutionUnit.cs b/src/ImageSharp/Metadata/PixelResolutionUnit.cs similarity index 100% rename from src/ImageSharp/MetaData/PixelResolutionUnit.cs rename to src/ImageSharp/Metadata/PixelResolutionUnit.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/DC-008-Translation-2019-E.pdf b/src/ImageSharp/Metadata/Profiles/Exif/DC-008-Translation-2019-E.pdf new file mode 100644 index 0000000000..9be0c8402b Binary files /dev/null and b/src/ImageSharp/Metadata/Profiles/Exif/DC-008-Translation-2019-E.pdf differ diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs new file mode 100644 index 0000000000..c58b224e49 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifConstants + { + public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] + { + (byte)'I', + (byte)'I', + 0x2A, + 0x00, + }; + + public static ReadOnlySpan BigEndianByteOrderMarker => new byte[] + { + (byte)'M', + (byte)'M', + 0x00, + 0x2A + }; + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs new file mode 100644 index 0000000000..74c86f7214 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Specifies exif data types. + /// + public enum ExifDataType + { + /// + /// Unknown + /// + Unknown = 0, + + /// + /// An 8-bit unsigned integer. + /// + Byte = 1, + + /// + /// An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. + /// + /// Although the standard defines ASCII this has commonly been ignored as + /// ASCII cannot properly encode text in many languages. + /// + /// + Ascii = 2, + + /// + /// A 16-bit (2-byte) unsigned integer. + /// + Short = 3, + + /// + /// A 32-bit (4-byte) unsigned integer. + /// + Long = 4, + + /// + /// Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + /// + Rational = 5, + + /// + /// An 8-bit signed integer. + /// + SignedByte = 6, + + /// + /// An 8-bit byte that can take any value depending on the field definition. + /// + Undefined = 7, + + /// + /// A 16-bit (2-byte) signed integer. + /// + SignedShort = 8, + + /// + /// A 32-bit (4-byte) signed integer (2's complement notation). + /// + SignedLong = 9, + + /// + /// Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + /// + SignedRational = 10, + + /// + /// A 32-bit single precision floating point value. + /// + SingleFloat = 11, + + /// + /// A 64-bit double precision floating point value. + /// + DoubleFloat = 12 + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs new file mode 100644 index 0000000000..d94dc56401 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifDataTypes + { + /// + /// Gets the size in bytes of the given data type. + /// + /// The data type. + /// + /// The . + /// + /// + /// Thrown if the type is unsupported. + /// + public static uint GetSize(ExifDataType dataType) + { + switch (dataType) + { + case ExifDataType.Ascii: + case ExifDataType.Byte: + case ExifDataType.SignedByte: + case ExifDataType.Undefined: + return 1; + case ExifDataType.Short: + case ExifDataType.SignedShort: + return 2; + case ExifDataType.Long: + case ExifDataType.SignedLong: + case ExifDataType.SingleFloat: + return 4; + case ExifDataType.DoubleFloat: + case ExifDataType.Rational: + case ExifDataType.SignedRational: + return 8; + default: + throw new NotSupportedException(dataType.ToString()); + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs new file mode 100644 index 0000000000..6405a7ff2b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Specifies which parts will be written when the profile is added to an image. + /// + [Flags] + public enum ExifParts + { + /// + /// None + /// + None = 0, + + /// + /// IfdTags + /// + IfdTags = 1, + + /// + /// ExifTags + /// + ExifTags = 4, + + /// + /// GPSTags + /// + GpsTags = 8, + + /// + /// All + /// + All = IfdTags | ExifTags | GpsTags + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs new file mode 100644 index 0000000000..29c21d6113 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -0,0 +1,304 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Represents an EXIF profile providing access to the collection of values. + /// + public sealed class ExifProfile : IDeepCloneable + { + /// + /// The byte array to read the EXIF profile from. + /// + private readonly byte[] data; + + /// + /// The collection of EXIF values + /// + private List values; + + /// + /// The thumbnail offset position in the byte stream + /// + private int thumbnailOffset; + + /// + /// The thumbnail length in the byte stream + /// + private int thumbnailLength; + + /// + /// Initializes a new instance of the class. + /// + public ExifProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The byte array to read the EXIF profile from. + public ExifProfile(byte[] data) + { + this.Parts = ExifParts.All; + this.data = data; + this.InvalidTags = Array.Empty(); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another EXIF profile. + /// + /// The other EXIF profile, where the clone should be made from. + /// is null.> + private ExifProfile(ExifProfile other) + { + Guard.NotNull(other, nameof(other)); + + this.Parts = other.Parts; + this.thumbnailLength = other.thumbnailLength; + this.thumbnailOffset = other.thumbnailOffset; + + this.InvalidTags = other.InvalidTags.Count > 0 + ? new List(other.InvalidTags) + : (IReadOnlyList)Array.Empty(); + + if (other.values != null) + { + this.values = new List(other.Values.Count); + + foreach (IExifValue value in other.Values) + { + this.values.Add(value.DeepClone()); + } + } + + if (other.data != null) + { + this.data = new byte[other.data.Length]; + other.data.AsSpan().CopyTo(this.data); + } + } + + /// + /// Gets or sets which parts will be written when the profile is added to an image. + /// + public ExifParts Parts { get; set; } + + /// + /// Gets the tags that where found but contained an invalid value. + /// + public IReadOnlyList InvalidTags { get; private set; } + + /// + /// Gets the values of this EXIF profile. + /// + public IReadOnlyList Values + { + get + { + this.InitializeValues(); + return this.values; + } + } + + /// + /// Returns the thumbnail in the EXIF profile when available. + /// + /// The pixel format. + /// + /// The . + /// + public Image CreateThumbnail() + where TPixel : unmanaged, IPixel + { + this.InitializeValues(); + + if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) + { + return null; + } + + if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) + { + return null; + } + + using (var memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) + { + return Image.Load(memStream); + } + } + + /// + /// Returns the value with the specified tag. + /// + /// The tag of the exif value. + /// The value with the specified tag. + /// The data type of the tag. + public IExifValue GetValue(ExifTag tag) + { + IExifValue value = this.GetValueInternal(tag); + return value is null ? null : (IExifValue)value; + } + + /// + /// Removes the value with the specified tag. + /// + /// The tag of the EXIF value. + /// + /// The . + /// + public bool RemoveValue(ExifTag tag) + { + this.InitializeValues(); + + for (int i = 0; i < this.values.Count; i++) + { + if (this.values[i].Tag == tag) + { + this.values.RemoveAt(i); + return true; + } + } + + return false; + } + + /// + /// Sets the value of the specified tag. + /// + /// The tag of the exif value. + /// The value. + /// The data type of the tag. + public void SetValue(ExifTag tag, TValueType value) + => this.SetValueInternal(tag, value); + + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + if (this.values is null) + { + return this.data; + } + + if (this.values.Count == 0) + { + return Array.Empty(); + } + + var writer = new ExifWriter(this.values, this.Parts); + return writer.GetData(); + } + + /// + public ExifProfile DeepClone() => new ExifProfile(this); + + /// + /// Returns the value with the specified tag. + /// + /// The tag of the exif value. + /// The value with the specified tag. + internal IExifValue GetValueInternal(ExifTag tag) + { + foreach (IExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) + { + return exifValue; + } + } + + return null; + } + + /// + /// Sets the value of the specified tag. + /// + /// The tag of the exif value. + /// The value. + internal void SetValueInternal(ExifTag tag, object value) + { + foreach (IExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) + { + exifValue.TrySetValue(value); + return; + } + } + + ExifValue newExifValue = ExifValues.Create(tag); + if (newExifValue is null) + { + throw new NotSupportedException(); + } + + newExifValue.TrySetValue(value); + this.values.Add(newExifValue); + } + + /// + /// Synchronizes the profiles with the specified metadata. + /// + /// The metadata. + internal void Sync(ImageMetadata metadata) + { + this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); + this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); + } + + private void SyncResolution(ExifTag tag, double resolution) + { + IExifValue value = this.GetValue(tag); + + if (value is null) + { + return; + } + + if (value.IsArray || value.DataType != ExifDataType.Rational) + { + this.RemoveValue(value.Tag); + } + + var newResolution = new Rational(resolution, false); + this.SetValue(tag, newResolution); + } + + private void InitializeValues() + { + if (this.values != null) + { + return; + } + + if (this.data is null) + { + this.values = new List(); + return; + } + + var reader = new ExifReader(this.data); + + this.values = reader.ReadValues(); + + this.InvalidTags = reader.InvalidTags.Count > 0 + ? new List(reader.InvalidTags) + : (IReadOnlyList)Array.Empty(); + + this.thumbnailOffset = (int)reader.ThumbnailOffset; + this.thumbnailLength = (int)reader.ThumbnailLength; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs new file mode 100644 index 0000000000..6ad8d24fa4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -0,0 +1,517 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Runtime.CompilerServices; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Reads and parses EXIF data from a byte array. + /// + internal sealed class ExifReader + { + private List invalidTags; + private readonly byte[] exifData; + private int position; + private bool isBigEndian; + private uint exifOffset; + private uint gpsOffset; + + public ExifReader(byte[] exifData) + { + this.exifData = exifData ?? throw new ArgumentNullException(nameof(exifData)); + } + + private delegate TDataType ConverterMethod(ReadOnlySpan data); + + /// + /// Gets the invalid tags. + /// + public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); + + /// + /// Gets the thumbnail length in the byte stream. + /// + public uint ThumbnailLength { get; private set; } + + /// + /// Gets the thumbnail offset position in the byte stream. + /// + public uint ThumbnailOffset { get; private set; } + + /// + /// Gets the remaining length. + /// + private int RemainingLength + { + get + { + if (this.position >= this.exifData.Length) + { + return 0; + } + + return this.exifData.Length - this.position; + } + } + + /// + /// Reads and returns the collection of EXIF values. + /// + /// + /// The . + /// + public List ReadValues() + { + var values = new List(); + + // II == 0x4949 + this.isBigEndian = this.ReadUInt16() != 0x4949; + + if (this.ReadUInt16() != 0x002A) + { + return values; + } + + uint ifdOffset = this.ReadUInt32(); + this.AddValues(values, ifdOffset); + + uint thumbnailOffset = this.ReadUInt32(); + this.GetThumbnail(thumbnailOffset); + + if (this.exifOffset != 0) + { + this.AddValues(values, this.exifOffset); + } + + if (this.gpsOffset != 0) + { + this.AddValues(values, this.gpsOffset); + } + + return values; + } + + private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) + { + int dataTypeSize = (int)ExifDataTypes.GetSize(dataType); + int length = data.Length / dataTypeSize; + + var result = new TDataType[length]; + + for (int i = 0; i < length; i++) + { + ReadOnlySpan buffer = data.Slice(i * dataTypeSize, dataTypeSize); + + result.SetValue(converter(buffer), i); + } + + return result; + } + + private byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; + + private string ConvertToString(ReadOnlySpan buffer) + { + int nullCharIndex = buffer.IndexOf((byte)0); + + if (nullCharIndex > -1) + { + buffer = buffer.Slice(0, nullCharIndex); + } + + return Encoding.UTF8.GetString(buffer); + } + + /// + /// Adds the collection of EXIF values to the reader. + /// + /// The values. + /// The index. + private void AddValues(List values, uint index) + { + if (index > (uint)this.exifData.Length) + { + return; + } + + this.position = (int)index; + int count = this.ReadUInt16(); + + for (int i = 0; i < count; i++) + { + if (!this.TryReadValue(out ExifValue value)) + { + continue; + } + + bool duplicate = false; + foreach (IExifValue val in values) + { + if (val == value) + { + duplicate = true; + break; + } + } + + if (duplicate) + { + continue; + } + + if (value == ExifTag.SubIFDOffset) + { + this.exifOffset = ((ExifLong)value).Value; + } + else if (value == ExifTag.GPSIFDOffset) + { + this.gpsOffset = ((ExifLong)value).Value; + } + else + { + values.Add(value); + } + } + } + + private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, uint numberOfComponents) + { + if (buffer.Length == 0) + { + return null; + } + + switch (dataType) + { + case ExifDataType.Unknown: + return null; + case ExifDataType.Ascii: + return this.ConvertToString(buffer); + case ExifDataType.Byte: + if (numberOfComponents == 1) + { + return this.ConvertToByte(buffer); + } + + return buffer.ToArray(); + case ExifDataType.DoubleFloat: + if (numberOfComponents == 1) + { + return this.ConvertToDouble(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToDouble); + case ExifDataType.Long: + if (numberOfComponents == 1) + { + return this.ConvertToUInt32(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToUInt32); + case ExifDataType.Rational: + if (numberOfComponents == 1) + { + return this.ToRational(buffer); + } + + return ToArray(dataType, buffer, this.ToRational); + case ExifDataType.Short: + if (numberOfComponents == 1) + { + return this.ConvertToShort(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToShort); + case ExifDataType.SignedByte: + if (numberOfComponents == 1) + { + return this.ConvertToSignedByte(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToSignedByte); + case ExifDataType.SignedLong: + if (numberOfComponents == 1) + { + return this.ConvertToInt32(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToInt32); + case ExifDataType.SignedRational: + if (numberOfComponents == 1) + { + return this.ToSignedRational(buffer); + } + + return ToArray(dataType, buffer, this.ToSignedRational); + case ExifDataType.SignedShort: + if (numberOfComponents == 1) + { + return this.ConvertToSignedShort(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToSignedShort); + case ExifDataType.SingleFloat: + if (numberOfComponents == 1) + { + return this.ConvertToSingle(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToSingle); + case ExifDataType.Undefined: + if (numberOfComponents == 1) + { + return this.ConvertToByte(buffer); + } + + return buffer.ToArray(); + default: + throw new NotSupportedException(); + } + } + + private bool TryReadValue(out ExifValue exifValue) + { + exifValue = default; + + // 2 | 2 | 4 | 4 + // tag | type | count | value offset + if (this.RemainingLength < 12) + { + return false; + } + + var tag = (ExifTagValue)this.ReadUInt16(); + ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + + // Ensure that the data type is valid + if (dataType == ExifDataType.Unknown) + { + return false; + } + + uint numberOfComponents = this.ReadUInt32(); + + // Issue #132: ExifDataType == Undefined is treated like a byte array. + // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) + if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + { + numberOfComponents = 4; + } + + uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); + + this.TryReadSpan(4, out ReadOnlySpan offsetBuffer); + + object value; + if (size > 4) + { + int oldIndex = this.position; + uint newIndex = this.ConvertToUInt32(offsetBuffer); + + // Ensure that the new index does not overrun the data + if (newIndex > int.MaxValue) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return false; + } + + this.position = (int)newIndex; + + if (this.RemainingLength < size) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + + this.position = oldIndex; + return false; + } + + this.TryReadSpan((int)size, out ReadOnlySpan dataBuffer); + + value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); + this.position = oldIndex; + } + else + { + value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents); + } + + exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return false; + } + + if (!exifValue.TrySetValue(value)) + { + return false; + } + + return true; + } + + private void AddInvalidTag(ExifTag tag) + => (this.invalidTags ?? (this.invalidTags = new List())).Add(tag); + + private bool TryReadSpan(int length, out ReadOnlySpan span) + { + if (this.RemainingLength < length) + { + span = default; + + return false; + } + + span = new ReadOnlySpan(this.exifData, this.position, length); + + this.position += length; + + return true; + } + + private uint ReadUInt32() + { + // Known as Long in Exif Specification + return this.TryReadSpan(4, out ReadOnlySpan span) + ? this.ConvertToUInt32(span) + : default; + } + + private ushort ReadUInt16() + { + return this.TryReadSpan(2, out ReadOnlySpan span) + ? this.ConvertToShort(span) + : default; + } + + private void GetThumbnail(uint offset) + { + var values = new List(); + this.AddValues(values, offset); + + foreach (ExifValue value in values) + { + if (value == ExifTag.JPEGInterchangeFormat) + { + this.ThumbnailOffset = ((ExifLong)value).Value; + } + else if (value == ExifTag.JPEGInterchangeFormatLength) + { + this.ThumbnailLength = ((ExifLong)value).Value; + } + } + } + + private double ConvertToDouble(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + long intValue = this.isBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(buffer) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + + return Unsafe.As(ref intValue); + } + + private uint ConvertToUInt32(ReadOnlySpan buffer) + { + // Known as Long in Exif Specification + if (buffer.Length < 4) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadUInt32BigEndian(buffer) + : BinaryPrimitives.ReadUInt32LittleEndian(buffer); + } + + private ushort ConvertToShort(ReadOnlySpan buffer) + { + if (buffer.Length < 2) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(buffer) + : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } + + private float ConvertToSingle(ReadOnlySpan buffer) + { + if (buffer.Length < 4) + { + return default; + } + + int intValue = this.isBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(buffer) + : BinaryPrimitives.ReadInt32LittleEndian(buffer); + + return Unsafe.As(ref intValue); + } + + private Rational ToRational(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + uint numerator = this.ConvertToUInt32(buffer.Slice(0, 4)); + uint denominator = this.ConvertToUInt32(buffer.Slice(4, 4)); + + return new Rational(numerator, denominator, false); + } + + private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); + + private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification + { + if (buffer.Length < 4) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(buffer) + : BinaryPrimitives.ReadInt32LittleEndian(buffer); + } + + private SignedRational ToSignedRational(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + int numerator = this.ConvertToInt32(buffer.Slice(0, 4)); + int denominator = this.ConvertToInt32(buffer.Slice(4, 4)); + + return new SignedRational(numerator, denominator, false); + } + + private short ConvertToSignedShort(ReadOnlySpan buffer) + { + if (buffer.Length < 2) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadInt16BigEndian(buffer) + : BinaryPrimitives.ReadInt16LittleEndian(buffer); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs new file mode 100644 index 0000000000..b9bb2ee056 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Class that provides a description for an ExifTag value. + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + internal sealed class ExifTagDescriptionAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The value of the exif tag. + /// The description for the value of the exif tag. + public ExifTagDescriptionAttribute(object value, string description) + { + } + + /// + /// Gets the tag description from any custom attributes. + /// + /// The tag. + /// The value. + /// + /// The . + /// + public static string GetDescription(ExifTag tag, object value) + { + var tagValue = (ExifTagValue)(ushort)tag; + FieldInfo field = tagValue.GetType().GetTypeInfo().GetDeclaredField(tagValue.ToString()); + + if (field is null) + { + return null; + } + + foreach (CustomAttributeData customAttribute in field.CustomAttributes) + { + object attributeValue = customAttribute.ConstructorArguments[0].Value; + + if (Equals(attributeValue, value)) + { + return (string)customAttribute.ConstructorArguments[1].Value; + } + } + + return null; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs new file mode 100644 index 0000000000..a4123d02fe --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs @@ -0,0 +1,275 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifTags + { + public static ExifParts GetPart(ExifTag tag) + { + switch ((ExifTagValue)(ushort)tag) + { + case ExifTagValue.SubfileType: + case ExifTagValue.OldSubfileType: + case ExifTagValue.ImageWidth: + case ExifTagValue.ImageLength: + case ExifTagValue.BitsPerSample: + case ExifTagValue.Compression: + case ExifTagValue.PhotometricInterpretation: + case ExifTagValue.Thresholding: + case ExifTagValue.CellWidth: + case ExifTagValue.CellLength: + case ExifTagValue.FillOrder: + case ExifTagValue.DocumentName: + case ExifTagValue.ImageDescription: + case ExifTagValue.Make: + case ExifTagValue.Model: + case ExifTagValue.StripOffsets: + case ExifTagValue.Orientation: + case ExifTagValue.SamplesPerPixel: + case ExifTagValue.RowsPerStrip: + case ExifTagValue.StripByteCounts: + case ExifTagValue.MinSampleValue: + case ExifTagValue.MaxSampleValue: + case ExifTagValue.XResolution: + case ExifTagValue.YResolution: + case ExifTagValue.PlanarConfiguration: + case ExifTagValue.PageName: + case ExifTagValue.XPosition: + case ExifTagValue.YPosition: + case ExifTagValue.FreeOffsets: + case ExifTagValue.FreeByteCounts: + case ExifTagValue.GrayResponseUnit: + case ExifTagValue.GrayResponseCurve: + case ExifTagValue.T4Options: + case ExifTagValue.T6Options: + case ExifTagValue.ResolutionUnit: + case ExifTagValue.PageNumber: + case ExifTagValue.ColorResponseUnit: + case ExifTagValue.TransferFunction: + case ExifTagValue.Software: + case ExifTagValue.DateTime: + case ExifTagValue.Artist: + case ExifTagValue.HostComputer: + case ExifTagValue.Predictor: + case ExifTagValue.WhitePoint: + case ExifTagValue.PrimaryChromaticities: + case ExifTagValue.ColorMap: + case ExifTagValue.HalftoneHints: + case ExifTagValue.TileWidth: + case ExifTagValue.TileLength: + case ExifTagValue.TileOffsets: + case ExifTagValue.TileByteCounts: + case ExifTagValue.BadFaxLines: + case ExifTagValue.CleanFaxData: + case ExifTagValue.ConsecutiveBadFaxLines: + case ExifTagValue.InkSet: + case ExifTagValue.InkNames: + case ExifTagValue.NumberOfInks: + case ExifTagValue.DotRange: + case ExifTagValue.TargetPrinter: + case ExifTagValue.ExtraSamples: + case ExifTagValue.SampleFormat: + case ExifTagValue.SMinSampleValue: + case ExifTagValue.SMaxSampleValue: + case ExifTagValue.TransferRange: + case ExifTagValue.ClipPath: + case ExifTagValue.XClipPathUnits: + case ExifTagValue.YClipPathUnits: + case ExifTagValue.Indexed: + case ExifTagValue.JPEGTables: + case ExifTagValue.OPIProxy: + case ExifTagValue.ProfileType: + case ExifTagValue.FaxProfile: + case ExifTagValue.CodingMethods: + case ExifTagValue.VersionYear: + case ExifTagValue.ModeNumber: + case ExifTagValue.Decode: + case ExifTagValue.DefaultImageColor: + case ExifTagValue.T82ptions: + case ExifTagValue.JPEGProc: + case ExifTagValue.JPEGInterchangeFormat: + case ExifTagValue.JPEGInterchangeFormatLength: + case ExifTagValue.JPEGRestartInterval: + case ExifTagValue.JPEGLosslessPredictors: + case ExifTagValue.JPEGPointTransforms: + case ExifTagValue.JPEGQTables: + case ExifTagValue.JPEGDCTables: + case ExifTagValue.JPEGACTables: + case ExifTagValue.YCbCrCoefficients: + case ExifTagValue.YCbCrPositioning: + case ExifTagValue.YCbCrSubsampling: + case ExifTagValue.ReferenceBlackWhite: + case ExifTagValue.StripRowCounts: + case ExifTagValue.XMP: + case ExifTagValue.Rating: + case ExifTagValue.RatingPercent: + case ExifTagValue.ImageID: + case ExifTagValue.CFARepeatPatternDim: + case ExifTagValue.CFAPattern2: + case ExifTagValue.BatteryLevel: + case ExifTagValue.Copyright: + case ExifTagValue.MDFileTag: + case ExifTagValue.MDScalePixel: + case ExifTagValue.MDLabName: + case ExifTagValue.MDSampleInfo: + case ExifTagValue.MDPrepDate: + case ExifTagValue.MDPrepTime: + case ExifTagValue.MDFileUnits: + case ExifTagValue.PixelScale: + case ExifTagValue.IntergraphPacketData: + case ExifTagValue.IntergraphRegisters: + case ExifTagValue.IntergraphMatrix: + case ExifTagValue.ModelTiePoint: + case ExifTagValue.SEMInfo: + case ExifTagValue.ModelTransform: + case ExifTagValue.ImageLayer: + case ExifTagValue.FaxRecvParams: + case ExifTagValue.FaxSubaddress: + case ExifTagValue.FaxRecvTime: + case ExifTagValue.ImageSourceData: + case ExifTagValue.XPTitle: + case ExifTagValue.XPComment: + case ExifTagValue.XPAuthor: + case ExifTagValue.XPKeywords: + case ExifTagValue.XPSubject: + case ExifTagValue.GDALMetadata: + case ExifTagValue.GDALNoData: + return ExifParts.IfdTags; + + case ExifTagValue.ExposureTime: + case ExifTagValue.FNumber: + case ExifTagValue.ExposureProgram: + case ExifTagValue.SpectralSensitivity: + case ExifTagValue.ISOSpeedRatings: + case ExifTagValue.OECF: + case ExifTagValue.Interlace: + case ExifTagValue.TimeZoneOffset: + case ExifTagValue.SelfTimerMode: + case ExifTagValue.SensitivityType: + case ExifTagValue.StandardOutputSensitivity: + case ExifTagValue.RecommendedExposureIndex: + case ExifTagValue.ISOSpeed: + case ExifTagValue.ISOSpeedLatitudeyyy: + case ExifTagValue.ISOSpeedLatitudezzz: + case ExifTagValue.ExifVersion: + case ExifTagValue.DateTimeOriginal: + case ExifTagValue.DateTimeDigitized: + case ExifTagValue.OffsetTime: + case ExifTagValue.OffsetTimeOriginal: + case ExifTagValue.OffsetTimeDigitized: + case ExifTagValue.ComponentsConfiguration: + case ExifTagValue.CompressedBitsPerPixel: + case ExifTagValue.ShutterSpeedValue: + case ExifTagValue.ApertureValue: + case ExifTagValue.BrightnessValue: + case ExifTagValue.ExposureBiasValue: + case ExifTagValue.MaxApertureValue: + case ExifTagValue.SubjectDistance: + case ExifTagValue.MeteringMode: + case ExifTagValue.LightSource: + case ExifTagValue.Flash: + case ExifTagValue.FocalLength: + case ExifTagValue.FlashEnergy2: + case ExifTagValue.SpatialFrequencyResponse2: + case ExifTagValue.Noise: + case ExifTagValue.FocalPlaneXResolution2: + case ExifTagValue.FocalPlaneYResolution2: + case ExifTagValue.FocalPlaneResolutionUnit2: + case ExifTagValue.ImageNumber: + case ExifTagValue.SecurityClassification: + case ExifTagValue.ImageHistory: + case ExifTagValue.SubjectArea: + case ExifTagValue.ExposureIndex2: + case ExifTagValue.TIFFEPStandardID: + case ExifTagValue.SensingMethod2: + case ExifTagValue.MakerNote: + case ExifTagValue.UserComment: + case ExifTagValue.SubsecTime: + case ExifTagValue.SubsecTimeOriginal: + case ExifTagValue.SubsecTimeDigitized: + case ExifTagValue.AmbientTemperature: + case ExifTagValue.Humidity: + case ExifTagValue.Pressure: + case ExifTagValue.WaterDepth: + case ExifTagValue.Acceleration: + case ExifTagValue.CameraElevationAngle: + case ExifTagValue.FlashpixVersion: + case ExifTagValue.ColorSpace: + case ExifTagValue.PixelXDimension: + case ExifTagValue.PixelYDimension: + case ExifTagValue.RelatedSoundFile: + case ExifTagValue.FlashEnergy: + case ExifTagValue.SpatialFrequencyResponse: + case ExifTagValue.FocalPlaneXResolution: + case ExifTagValue.FocalPlaneYResolution: + case ExifTagValue.FocalPlaneResolutionUnit: + case ExifTagValue.SubjectLocation: + case ExifTagValue.ExposureIndex: + case ExifTagValue.SensingMethod: + case ExifTagValue.FileSource: + case ExifTagValue.SceneType: + case ExifTagValue.CFAPattern: + case ExifTagValue.CustomRendered: + case ExifTagValue.ExposureMode: + case ExifTagValue.WhiteBalance: + case ExifTagValue.DigitalZoomRatio: + case ExifTagValue.FocalLengthIn35mmFilm: + case ExifTagValue.SceneCaptureType: + case ExifTagValue.GainControl: + case ExifTagValue.Contrast: + case ExifTagValue.Saturation: + case ExifTagValue.Sharpness: + case ExifTagValue.DeviceSettingDescription: + case ExifTagValue.SubjectDistanceRange: + case ExifTagValue.ImageUniqueID: + case ExifTagValue.OwnerName: + case ExifTagValue.SerialNumber: + case ExifTagValue.LensSpecification: + case ExifTagValue.LensMake: + case ExifTagValue.LensModel: + case ExifTagValue.LensSerialNumber: + return ExifParts.ExifTags; + + case ExifTagValue.GPSVersionID: + case ExifTagValue.GPSLatitudeRef: + case ExifTagValue.GPSLatitude: + case ExifTagValue.GPSLongitudeRef: + case ExifTagValue.GPSLongitude: + case ExifTagValue.GPSAltitudeRef: + case ExifTagValue.GPSAltitude: + case ExifTagValue.GPSTimestamp: + case ExifTagValue.GPSSatellites: + case ExifTagValue.GPSStatus: + case ExifTagValue.GPSMeasureMode: + case ExifTagValue.GPSDOP: + case ExifTagValue.GPSSpeedRef: + case ExifTagValue.GPSSpeed: + case ExifTagValue.GPSTrackRef: + case ExifTagValue.GPSTrack: + case ExifTagValue.GPSImgDirectionRef: + case ExifTagValue.GPSImgDirection: + case ExifTagValue.GPSMapDatum: + case ExifTagValue.GPSDestLatitudeRef: + case ExifTagValue.GPSDestLatitude: + case ExifTagValue.GPSDestLongitudeRef: + case ExifTagValue.GPSDestLongitude: + case ExifTagValue.GPSDestBearingRef: + case ExifTagValue.GPSDestBearing: + case ExifTagValue.GPSDestDistanceRef: + case ExifTagValue.GPSDestDistance: + case ExifTagValue.GPSProcessingMethod: + case ExifTagValue.GPSAreaInformation: + case ExifTagValue.GPSDateStamp: + case ExifTagValue.GPSDifferential: + return ExifParts.GpsTags; + + case ExifTagValue.Unknown: + case ExifTagValue.SubIFDOffset: + case ExifTagValue.GPSIFDOffset: + default: + return ExifParts.None; + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs new file mode 100644 index 0000000000..b00813730d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -0,0 +1,423 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Contains methods for writing EXIF metadata. + /// + internal sealed class ExifWriter + { + /// + /// Which parts will be written. + /// + private readonly ExifParts allowedParts; + private readonly IList values; + private List dataOffsets; + private readonly List ifdValues; + private readonly List exifValues; + private readonly List gpsValues; + + /// + /// Initializes a new instance of the class. + /// + /// The values. + /// The allowed parts. + public ExifWriter(IList values, ExifParts allowedParts) + { + this.values = values; + this.allowedParts = allowedParts; + this.ifdValues = this.GetPartValues(ExifParts.IfdTags); + this.exifValues = this.GetPartValues(ExifParts.ExifTags); + this.gpsValues = this.GetPartValues(ExifParts.GpsTags); + } + + /// + /// Returns the EXIF data. + /// + /// + /// The . + /// + public byte[] GetData() + { + const uint startIndex = 0; + uint length; + + IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); + IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); + + if (this.ifdValues.Count == 0 && this.exifValues.Count == 0 && this.gpsValues.Count == 0) + { + return Array.Empty(); + } + + uint ifdLength = this.GetLength(this.ifdValues) + 4U; + uint exifLength = this.GetLength(this.exifValues); + uint gpsLength = this.GetLength(this.gpsValues); + + length = ifdLength + exifLength + gpsLength; + + if (length == 4U) + { + return Array.Empty(); + } + + // two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total + length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; + + length += 4 + 2; + + var result = new byte[length]; + + int i = 0; + + // The byte order marker for little-endian, followed by the number 42 and a 0 + ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); + i += ExifConstants.LittleEndianByteOrderMarker.Length; + + uint ifdOffset = ((uint)i - startIndex) + 4U; + uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; + + exifOffset?.TrySetValue(ifdOffset + ifdLength); + gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength); + + i = WriteUInt32(ifdOffset, result, i); + i = this.WriteHeaders(this.ifdValues, result, i); + i = WriteUInt32(thumbnailOffset, result, i); + i = this.WriteData(startIndex, this.ifdValues, result, i); + + if (exifLength > 0) + { + i = this.WriteHeaders(this.exifValues, result, i); + i = this.WriteData(startIndex, this.exifValues, result, i); + } + + if (gpsLength > 0) + { + i = this.WriteHeaders(this.gpsValues, result, i); + i = this.WriteData(startIndex, this.gpsValues, result, i); + } + + WriteUInt16(0, result, i); + + return result; + } + + private static unsafe int WriteSingle(float value, Span destination, int offset) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), *((int*)&value)); + + return offset + 4; + } + + private static unsafe int WriteDouble(double value, Span destination, int offset) + { + BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), *((long*)&value)); + + return offset + 8; + } + + private static int Write(ReadOnlySpan source, Span destination, int offset) + { + source.CopyTo(destination.Slice(offset, source.Length)); + + return offset + source.Length; + } + + private static int WriteInt16(short value, Span destination, int offset) + { + BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(offset, 2), value); + + return offset + 2; + } + + private static int WriteUInt16(ushort value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(offset, 2), value); + + return offset + 2; + } + + private static int WriteUInt32(uint value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(offset, 4), value); + + return offset + 4; + } + + private static int WriteInt32(int value, Span destination, int offset) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); + + return offset + 4; + } + + private static IExifValue GetOffsetValue(List ifdValues, List values, ExifTag offset) + { + int index = -1; + + for (int i = 0; i < ifdValues.Count; i++) + { + if (ifdValues[i].Tag == offset) + { + index = i; + } + } + + if (values.Count > 0) + { + if (index != -1) + { + return ifdValues[index]; + } + + ExifValue result = ExifValues.Create(offset); + ifdValues.Add(result); + + return result; + } + else if (index != -1) + { + ifdValues.RemoveAt(index); + } + + return null; + } + + private List GetPartValues(ExifParts part) + { + var result = new List(); + + if (!EnumUtils.HasFlag(this.allowedParts, part)) + { + return result; + } + + foreach (IExifValue value in this.values) + { + if (!HasValue(value)) + { + continue; + } + + if (ExifTags.GetPart(value.Tag) == part) + { + result.Add(value); + } + } + + return result; + } + + private static bool HasValue(IExifValue exifValue) + { + object value = exifValue.GetValue(); + if (value is null) + { + return false; + } + + if (exifValue.DataType == ExifDataType.Ascii) + { + string stringValue = (string)value; + return stringValue.Length > 0; + } + + if (value is Array arrayValue) + { + return arrayValue.Length > 0; + } + + return true; + } + + private uint GetLength(IList values) + { + if (values.Count == 0) + { + return 0; + } + + uint length = 2; + + foreach (IExifValue value in values) + { + uint valueLength = GetLength(value); + + length += 2 + 2 + 4 + 4; + + if (valueLength > 4) + { + length += valueLength; + } + } + + return length; + } + + private static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); + + private static uint GetNumberOfComponents(IExifValue exifValue) + { + object value = exifValue.GetValue(); + + if (exifValue.DataType == ExifDataType.Ascii) + { + return (uint)Encoding.UTF8.GetBytes((string)value).Length + 1; + } + + if (value is Array arrayValue) + { + return (uint)arrayValue.Length; + } + + return 1; + } + + private int WriteArray(IExifValue value, Span destination, int offset) + { + if (value.DataType == ExifDataType.Ascii) + { + return this.WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset); + } + + int newOffset = offset; + foreach (object obj in (Array)value.GetValue()) + { + newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); + } + + return newOffset; + } + + private int WriteData(uint startIndex, List values, Span destination, int offset) + { + if (this.dataOffsets.Count == 0) + { + return offset; + } + + int newOffset = offset; + + int i = 0; + foreach (IExifValue value in values) + { + if (GetLength(value) > 4) + { + WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); + newOffset = this.WriteValue(value, destination, newOffset); + } + } + + return newOffset; + } + + private int WriteHeaders(List values, Span destination, int offset) + { + this.dataOffsets = new List(); + + int newOffset = WriteUInt16((ushort)values.Count, destination, offset); + + if (values.Count == 0) + { + return newOffset; + } + + foreach (IExifValue value in values) + { + newOffset = WriteUInt16((ushort)value.Tag, destination, newOffset); + newOffset = WriteUInt16((ushort)value.DataType, destination, newOffset); + newOffset = WriteUInt32(GetNumberOfComponents(value), destination, newOffset); + + uint length = GetLength(value); + if (length > 4) + { + this.dataOffsets.Add(newOffset); + } + else + { + this.WriteValue(value, destination, newOffset); + } + + newOffset += 4; + } + + return newOffset; + } + + private static void WriteRational(Span destination, in Rational value) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(0, 4), value.Numerator); + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + } + + private static void WriteSignedRational(Span destination, in SignedRational value) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(0, 4), value.Numerator); + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + } + + private int WriteValue(ExifDataType dataType, object value, Span destination, int offset) + { + switch (dataType) + { + case ExifDataType.Ascii: + offset = Write(Encoding.UTF8.GetBytes((string)value), destination, offset); + destination[offset] = 0; + return offset + 1; + case ExifDataType.Byte: + case ExifDataType.Undefined: + destination[offset] = (byte)value; + return offset + 1; + case ExifDataType.DoubleFloat: + return WriteDouble((double)value, destination, offset); + case ExifDataType.Short: + if (value is Number shortNumber) + { + return WriteUInt16((ushort)shortNumber, destination, offset); + } + + return WriteUInt16((ushort)value, destination, offset); + case ExifDataType.Long: + if (value is Number longNumber) + { + return WriteUInt32((uint)longNumber, destination, offset); + } + + return WriteUInt32((uint)value, destination, offset); + case ExifDataType.Rational: + WriteRational(destination.Slice(offset, 8), (Rational)value); + return offset + 8; + case ExifDataType.SignedByte: + destination[offset] = unchecked((byte)((sbyte)value)); + return offset + 1; + case ExifDataType.SignedLong: + return WriteInt32((int)value, destination, offset); + case ExifDataType.SignedShort: + return WriteInt16((short)value, destination, offset); + case ExifDataType.SignedRational: + WriteSignedRational(destination.Slice(offset, 8), (SignedRational)value); + return offset + 8; + case ExifDataType.SingleFloat: + return WriteSingle((float)value, destination, offset); + default: + throw new NotImplementedException(); + } + } + + private int WriteValue(IExifValue value, Span destination, int offset) + { + if (value.IsArray && value.DataType != ExifDataType.Ascii) + { + return this.WriteArray(value, destination, offset); + } + + return this.WriteValue(value.DataType, value.GetValue(), destination, offset); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/README.md b/src/ImageSharp/Metadata/Profiles/Exif/README.md new file mode 100644 index 0000000000..7901527e1a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/README.md @@ -0,0 +1,3 @@ +Adapted from Magick.NET: + +https://github.com/dlemstra/Magick.NET diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs new file mode 100644 index 0000000000..dc33fd8b0a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the FaxProfile exif tag. + /// + public static ExifTag FaxProfile { get; } = new ExifTag(ExifTagValue.FaxProfile); + + /// + /// Gets the ModeNumber exif tag. + /// + public static ExifTag ModeNumber { get; } = new ExifTag(ExifTagValue.ModeNumber); + + /// + /// Gets the GPSAltitudeRef exif tag. + /// + public static ExifTag GPSAltitudeRef { get; } = new ExifTag(ExifTagValue.GPSAltitudeRef); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs new file mode 100644 index 0000000000..2bfa8ff213 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ClipPath exif tag. + /// + public static ExifTag ClipPath => new ExifTag(ExifTagValue.ClipPath); + + /// + /// Gets the VersionYear exif tag. + /// + public static ExifTag VersionYear => new ExifTag(ExifTagValue.VersionYear); + + /// + /// Gets the XMP exif tag. + /// + public static ExifTag XMP => new ExifTag(ExifTagValue.XMP); + + /// + /// Gets the CFAPattern2 exif tag. + /// + public static ExifTag CFAPattern2 => new ExifTag(ExifTagValue.CFAPattern2); + + /// + /// Gets the TIFFEPStandardID exif tag. + /// + public static ExifTag TIFFEPStandardID => new ExifTag(ExifTagValue.TIFFEPStandardID); + + /// + /// Gets the XPTitle exif tag. + /// + public static ExifTag XPTitle => new ExifTag(ExifTagValue.XPTitle); + + /// + /// Gets the XPComment exif tag. + /// + public static ExifTag XPComment => new ExifTag(ExifTagValue.XPComment); + + /// + /// Gets the XPAuthor exif tag. + /// + public static ExifTag XPAuthor => new ExifTag(ExifTagValue.XPAuthor); + + /// + /// Gets the XPKeywords exif tag. + /// + public static ExifTag XPKeywords => new ExifTag(ExifTagValue.XPKeywords); + + /// + /// Gets the XPSubject exif tag. + /// + public static ExifTag XPSubject => new ExifTag(ExifTagValue.XPSubject); + + /// + /// Gets the GPSVersionID exif tag. + /// + public static ExifTag GPSVersionID => new ExifTag(ExifTagValue.GPSVersionID); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs new file mode 100644 index 0000000000..6cbae4c558 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the PixelScale exif tag. + /// + public static ExifTag PixelScale { get; } = new ExifTag(ExifTagValue.PixelScale); + + /// + /// Gets the IntergraphMatrix exif tag. + /// + public static ExifTag IntergraphMatrix { get; } = new ExifTag(ExifTagValue.IntergraphMatrix); + + /// + /// Gets the ModelTiePoint exif tag. + /// + public static ExifTag ModelTiePoint { get; } = new ExifTag(ExifTagValue.ModelTiePoint); + + /// + /// Gets the ModelTransform exif tag. + /// + public static ExifTag ModelTransform { get; } = new ExifTag(ExifTagValue.ModelTransform); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs new file mode 100644 index 0000000000..571b50efb6 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the SubfileType exif tag. + /// + public static ExifTag SubfileType { get; } = new ExifTag(ExifTagValue.SubfileType); + + /// + /// Gets the SubIFDOffset exif tag. + /// + public static ExifTag SubIFDOffset { get; } = new ExifTag(ExifTagValue.SubIFDOffset); + + /// + /// Gets the GPSIFDOffset exif tag. + /// + public static ExifTag GPSIFDOffset { get; } = new ExifTag(ExifTagValue.GPSIFDOffset); + + /// + /// Gets the T4Options exif tag. + /// + public static ExifTag T4Options { get; } = new ExifTag(ExifTagValue.T4Options); + + /// + /// Gets the T6Options exif tag. + /// + public static ExifTag T6Options { get; } = new ExifTag(ExifTagValue.T6Options); + + /// + /// Gets the XClipPathUnits exif tag. + /// + public static ExifTag XClipPathUnits { get; } = new ExifTag(ExifTagValue.XClipPathUnits); + + /// + /// Gets the YClipPathUnits exif tag. + /// + public static ExifTag YClipPathUnits { get; } = new ExifTag(ExifTagValue.YClipPathUnits); + + /// + /// Gets the ProfileType exif tag. + /// + public static ExifTag ProfileType { get; } = new ExifTag(ExifTagValue.ProfileType); + + /// + /// Gets the CodingMethods exif tag. + /// + public static ExifTag CodingMethods { get; } = new ExifTag(ExifTagValue.CodingMethods); + + /// + /// Gets the T82ptions exif tag. + /// + public static ExifTag T82ptions { get; } = new ExifTag(ExifTagValue.T82ptions); + + /// + /// Gets the JPEGInterchangeFormat exif tag. + /// + public static ExifTag JPEGInterchangeFormat { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormat); + + /// + /// Gets the JPEGInterchangeFormatLength exif tag. + /// + public static ExifTag JPEGInterchangeFormatLength { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormatLength); + + /// + /// Gets the MDFileTag exif tag. + /// + public static ExifTag MDFileTag { get; } = new ExifTag(ExifTagValue.MDFileTag); + + /// + /// Gets the StandardOutputSensitivity exif tag. + /// + public static ExifTag StandardOutputSensitivity { get; } = new ExifTag(ExifTagValue.StandardOutputSensitivity); + + /// + /// Gets the RecommendedExposureIndex exif tag. + /// + public static ExifTag RecommendedExposureIndex { get; } = new ExifTag(ExifTagValue.RecommendedExposureIndex); + + /// + /// Gets the ISOSpeed exif tag. + /// + public static ExifTag ISOSpeed { get; } = new ExifTag(ExifTagValue.ISOSpeed); + + /// + /// Gets the ISOSpeedLatitudeyyy exif tag. + /// + public static ExifTag ISOSpeedLatitudeyyy { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudeyyy); + + /// + /// Gets the ISOSpeedLatitudezzz exif tag. + /// + public static ExifTag ISOSpeedLatitudezzz { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudezzz); + + /// + /// Gets the FaxRecvParams exif tag. + /// + public static ExifTag FaxRecvParams { get; } = new ExifTag(ExifTagValue.FaxRecvParams); + + /// + /// Gets the FaxRecvTime exif tag. + /// + public static ExifTag FaxRecvTime { get; } = new ExifTag(ExifTagValue.FaxRecvTime); + + /// + /// Gets the ImageNumber exif tag. + /// + public static ExifTag ImageNumber { get; } = new ExifTag(ExifTagValue.ImageNumber); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs new file mode 100644 index 0000000000..120f2dab0f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the FreeOffsets exif tag. + /// + public static ExifTag FreeOffsets { get; } = new ExifTag(ExifTagValue.FreeOffsets); + + /// + /// Gets the FreeByteCounts exif tag. + /// + public static ExifTag FreeByteCounts { get; } = new ExifTag(ExifTagValue.FreeByteCounts); + + /// + /// Gets the ColorResponseUnit exif tag. + /// + public static ExifTag ColorResponseUnit { get; } = new ExifTag(ExifTagValue.ColorResponseUnit); + + /// + /// Gets the TileOffsets exif tag. + /// + public static ExifTag TileOffsets { get; } = new ExifTag(ExifTagValue.TileOffsets); + + /// + /// Gets the SMinSampleValue exif tag. + /// + public static ExifTag SMinSampleValue { get; } = new ExifTag(ExifTagValue.SMinSampleValue); + + /// + /// Gets the SMaxSampleValue exif tag. + /// + public static ExifTag SMaxSampleValue { get; } = new ExifTag(ExifTagValue.SMaxSampleValue); + + /// + /// Gets the JPEGQTables exif tag. + /// + public static ExifTag JPEGQTables { get; } = new ExifTag(ExifTagValue.JPEGQTables); + + /// + /// Gets the JPEGDCTables exif tag. + /// + public static ExifTag JPEGDCTables { get; } = new ExifTag(ExifTagValue.JPEGDCTables); + + /// + /// Gets the JPEGACTables exif tag. + /// + public static ExifTag JPEGACTables { get; } = new ExifTag(ExifTagValue.JPEGACTables); + + /// + /// Gets the StripRowCounts exif tag. + /// + public static ExifTag StripRowCounts { get; } = new ExifTag(ExifTagValue.StripRowCounts); + + /// + /// Gets the IntergraphRegisters exif tag. + /// + public static ExifTag IntergraphRegisters { get; } = new ExifTag(ExifTagValue.IntergraphRegisters); + + /// + /// Gets the TimeZoneOffset exif tag. + /// + public static ExifTag TimeZoneOffset { get; } = new ExifTag(ExifTagValue.TimeZoneOffset); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs new file mode 100644 index 0000000000..6cea52b1a5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ImageWidth exif tag. + /// + public static ExifTag ImageWidth { get; } = new ExifTag(ExifTagValue.ImageWidth); + + /// + /// Gets the ImageLength exif tag. + /// + public static ExifTag ImageLength { get; } = new ExifTag(ExifTagValue.ImageLength); + + /// + /// Gets the TileWidth exif tag. + /// + public static ExifTag TileWidth { get; } = new ExifTag(ExifTagValue.TileWidth); + + /// + /// Gets the TileLength exif tag. + /// + public static ExifTag TileLength { get; } = new ExifTag(ExifTagValue.TileLength); + + /// + /// Gets the BadFaxLines exif tag. + /// + public static ExifTag BadFaxLines { get; } = new ExifTag(ExifTagValue.BadFaxLines); + + /// + /// Gets the ConsecutiveBadFaxLines exif tag. + /// + public static ExifTag ConsecutiveBadFaxLines { get; } = new ExifTag(ExifTagValue.ConsecutiveBadFaxLines); + + /// + /// Gets the PixelXDimension exif tag. + /// + public static ExifTag PixelXDimension { get; } = new ExifTag(ExifTagValue.PixelXDimension); + + /// + /// Gets the PixelYDimension exif tag. + /// + public static ExifTag PixelYDimension { get; } = new ExifTag(ExifTagValue.PixelYDimension); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs new file mode 100644 index 0000000000..b515ab36a5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the StripOffsets exif tag. + /// + public static ExifTag StripOffsets { get; } = new ExifTag(ExifTagValue.StripOffsets); + + /// + /// Gets the TileByteCounts exif tag. + /// + public static ExifTag TileByteCounts { get; } = new ExifTag(ExifTagValue.TileByteCounts); + + /// + /// Gets the ImageLayer exif tag. + /// + public static ExifTag ImageLayer { get; } = new ExifTag(ExifTagValue.ImageLayer); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs new file mode 100644 index 0000000000..2281dee49c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the XPosition exif tag. + /// + public static ExifTag XPosition { get; } = new ExifTag(ExifTagValue.XPosition); + + /// + /// Gets the YPosition exif tag. + /// + public static ExifTag YPosition { get; } = new ExifTag(ExifTagValue.YPosition); + + /// + /// Gets the XResolution exif tag. + /// + public static ExifTag XResolution { get; } = new ExifTag(ExifTagValue.XResolution); + + /// + /// Gets the YResolution exif tag. + /// + public static ExifTag YResolution { get; } = new ExifTag(ExifTagValue.YResolution); + + /// + /// Gets the BatteryLevel exif tag. + /// + public static ExifTag BatteryLevel { get; } = new ExifTag(ExifTagValue.BatteryLevel); + + /// + /// Gets the ExposureTime exif tag. + /// + public static ExifTag ExposureTime { get; } = new ExifTag(ExifTagValue.ExposureTime); + + /// + /// Gets the FNumber exif tag. + /// + public static ExifTag FNumber { get; } = new ExifTag(ExifTagValue.FNumber); + + /// + /// Gets the MDScalePixel exif tag. + /// + public static ExifTag MDScalePixel { get; } = new ExifTag(ExifTagValue.MDScalePixel); + + /// + /// Gets the CompressedBitsPerPixel exif tag. + /// + public static ExifTag CompressedBitsPerPixel { get; } = new ExifTag(ExifTagValue.CompressedBitsPerPixel); + + /// + /// Gets the ApertureValue exif tag. + /// + public static ExifTag ApertureValue { get; } = new ExifTag(ExifTagValue.ApertureValue); + + /// + /// Gets the MaxApertureValue exif tag. + /// + public static ExifTag MaxApertureValue { get; } = new ExifTag(ExifTagValue.MaxApertureValue); + + /// + /// Gets the SubjectDistance exif tag. + /// + public static ExifTag SubjectDistance { get; } = new ExifTag(ExifTagValue.SubjectDistance); + + /// + /// Gets the FocalLength exif tag. + /// + public static ExifTag FocalLength { get; } = new ExifTag(ExifTagValue.FocalLength); + + /// + /// Gets the FlashEnergy2 exif tag. + /// + public static ExifTag FlashEnergy2 { get; } = new ExifTag(ExifTagValue.FlashEnergy2); + + /// + /// Gets the FocalPlaneXResolution2 exif tag. + /// + public static ExifTag FocalPlaneXResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution2); + + /// + /// Gets the FocalPlaneYResolution2 exif tag. + /// + public static ExifTag FocalPlaneYResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution2); + + /// + /// Gets the ExposureIndex2 exif tag. + /// + public static ExifTag ExposureIndex2 { get; } = new ExifTag(ExifTagValue.ExposureIndex2); + + /// + /// Gets the Humidity exif tag. + /// + public static ExifTag Humidity { get; } = new ExifTag(ExifTagValue.Humidity); + + /// + /// Gets the Pressure exif tag. + /// + public static ExifTag Pressure { get; } = new ExifTag(ExifTagValue.Pressure); + + /// + /// Gets the Acceleration exif tag. + /// + public static ExifTag Acceleration { get; } = new ExifTag(ExifTagValue.Acceleration); + + /// + /// Gets the FlashEnergy exif tag. + /// + public static ExifTag FlashEnergy { get; } = new ExifTag(ExifTagValue.FlashEnergy); + + /// + /// Gets the FocalPlaneXResolution exif tag. + /// + public static ExifTag FocalPlaneXResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution); + + /// + /// Gets the FocalPlaneYResolution exif tag. + /// + public static ExifTag FocalPlaneYResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution); + + /// + /// Gets the ExposureIndex exif tag. + /// + public static ExifTag ExposureIndex { get; } = new ExifTag(ExifTagValue.ExposureIndex); + + /// + /// Gets the DigitalZoomRatio exif tag. + /// + public static ExifTag DigitalZoomRatio { get; } = new ExifTag(ExifTagValue.DigitalZoomRatio); + + /// + /// Gets the GPSAltitude exif tag. + /// + public static ExifTag GPSAltitude { get; } = new ExifTag(ExifTagValue.GPSAltitude); + + /// + /// Gets the GPSDOP exif tag. + /// + public static ExifTag GPSDOP { get; } = new ExifTag(ExifTagValue.GPSDOP); + + /// + /// Gets the GPSSpeed exif tag. + /// + public static ExifTag GPSSpeed { get; } = new ExifTag(ExifTagValue.GPSSpeed); + + /// + /// Gets the GPSTrack exif tag. + /// + public static ExifTag GPSTrack { get; } = new ExifTag(ExifTagValue.GPSTrack); + + /// + /// Gets the GPSImgDirection exif tag. + /// + public static ExifTag GPSImgDirection { get; } = new ExifTag(ExifTagValue.GPSImgDirection); + + /// + /// Gets the GPSDestBearing exif tag. + /// + public static ExifTag GPSDestBearing { get; } = new ExifTag(ExifTagValue.GPSDestBearing); + + /// + /// Gets the GPSDestDistance exif tag. + /// + public static ExifTag GPSDestDistance { get; } = new ExifTag(ExifTagValue.GPSDestDistance); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs new file mode 100644 index 0000000000..cf43a8a8a4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the WhitePoint exif tag. + /// + public static ExifTag WhitePoint { get; } = new ExifTag(ExifTagValue.WhitePoint); + + /// + /// Gets the PrimaryChromaticities exif tag. + /// + public static ExifTag PrimaryChromaticities { get; } = new ExifTag(ExifTagValue.PrimaryChromaticities); + + /// + /// Gets the YCbCrCoefficients exif tag. + /// + public static ExifTag YCbCrCoefficients { get; } = new ExifTag(ExifTagValue.YCbCrCoefficients); + + /// + /// Gets the ReferenceBlackWhite exif tag. + /// + public static ExifTag ReferenceBlackWhite { get; } = new ExifTag(ExifTagValue.ReferenceBlackWhite); + + /// + /// Gets the GPSLatitude exif tag. + /// + public static ExifTag GPSLatitude { get; } = new ExifTag(ExifTagValue.GPSLatitude); + + /// + /// Gets the GPSLongitude exif tag. + /// + public static ExifTag GPSLongitude { get; } = new ExifTag(ExifTagValue.GPSLongitude); + + /// + /// Gets the GPSTimestamp exif tag. + /// + public static ExifTag GPSTimestamp { get; } = new ExifTag(ExifTagValue.GPSTimestamp); + + /// + /// Gets the GPSDestLatitude exif tag. + /// + public static ExifTag GPSDestLatitude { get; } = new ExifTag(ExifTagValue.GPSDestLatitude); + + /// + /// Gets the GPSDestLongitude exif tag. + /// + public static ExifTag GPSDestLongitude { get; } = new ExifTag(ExifTagValue.GPSDestLongitude); + + /// + /// Gets the LensSpecification exif tag. + /// + public static ExifTag LensSpecification { get; } = new ExifTag(ExifTagValue.LensSpecification); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs new file mode 100644 index 0000000000..7fe9a58bcd --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs @@ -0,0 +1,239 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the OldSubfileType exif tag. + /// + public static ExifTag OldSubfileType { get; } = new ExifTag(ExifTagValue.OldSubfileType); + + /// + /// Gets the Compression exif tag. + /// + public static ExifTag Compression { get; } = new ExifTag(ExifTagValue.Compression); + + /// + /// Gets the PhotometricInterpretation exif tag. + /// + public static ExifTag PhotometricInterpretation { get; } = new ExifTag(ExifTagValue.PhotometricInterpretation); + + /// + /// Gets the Thresholding exif tag. + /// + public static ExifTag Thresholding { get; } = new ExifTag(ExifTagValue.Thresholding); + + /// + /// Gets the CellWidth exif tag. + /// + public static ExifTag CellWidth { get; } = new ExifTag(ExifTagValue.CellWidth); + + /// + /// Gets the CellLength exif tag. + /// + public static ExifTag CellLength { get; } = new ExifTag(ExifTagValue.CellLength); + + /// + /// Gets the FillOrder exif tag. + /// + public static ExifTag FillOrder { get; } = new ExifTag(ExifTagValue.FillOrder); + + /// + /// Gets the Orientation exif tag. + /// + public static ExifTag Orientation { get; } = new ExifTag(ExifTagValue.Orientation); + + /// + /// Gets the SamplesPerPixel exif tag. + /// + public static ExifTag SamplesPerPixel { get; } = new ExifTag(ExifTagValue.SamplesPerPixel); + + /// + /// Gets the PlanarConfiguration exif tag. + /// + public static ExifTag PlanarConfiguration { get; } = new ExifTag(ExifTagValue.PlanarConfiguration); + + /// + /// Gets the GrayResponseUnit exif tag. + /// + public static ExifTag GrayResponseUnit { get; } = new ExifTag(ExifTagValue.GrayResponseUnit); + + /// + /// Gets the ResolutionUnit exif tag. + /// + public static ExifTag ResolutionUnit { get; } = new ExifTag(ExifTagValue.ResolutionUnit); + + /// + /// Gets the CleanFaxData exif tag. + /// + public static ExifTag CleanFaxData { get; } = new ExifTag(ExifTagValue.CleanFaxData); + + /// + /// Gets the InkSet exif tag. + /// + public static ExifTag InkSet { get; } = new ExifTag(ExifTagValue.InkSet); + + /// + /// Gets the NumberOfInks exif tag. + /// + public static ExifTag NumberOfInks { get; } = new ExifTag(ExifTagValue.NumberOfInks); + + /// + /// Gets the DotRange exif tag. + /// + public static ExifTag DotRange { get; } = new ExifTag(ExifTagValue.DotRange); + + /// + /// Gets the Indexed exif tag. + /// + public static ExifTag Indexed { get; } = new ExifTag(ExifTagValue.Indexed); + + /// + /// Gets the OPIProxy exif tag. + /// + public static ExifTag OPIProxy { get; } = new ExifTag(ExifTagValue.OPIProxy); + + /// + /// Gets the JPEGProc exif tag. + /// + public static ExifTag JPEGProc { get; } = new ExifTag(ExifTagValue.JPEGProc); + + /// + /// Gets the JPEGRestartInterval exif tag. + /// + public static ExifTag JPEGRestartInterval { get; } = new ExifTag(ExifTagValue.JPEGRestartInterval); + + /// + /// Gets the YCbCrPositioning exif tag. + /// + public static ExifTag YCbCrPositioning { get; } = new ExifTag(ExifTagValue.YCbCrPositioning); + + /// + /// Gets the Rating exif tag. + /// + public static ExifTag Rating { get; } = new ExifTag(ExifTagValue.Rating); + + /// + /// Gets the RatingPercent exif tag. + /// + public static ExifTag RatingPercent { get; } = new ExifTag(ExifTagValue.RatingPercent); + + /// + /// Gets the ExposureProgram exif tag. + /// + public static ExifTag ExposureProgram { get; } = new ExifTag(ExifTagValue.ExposureProgram); + + /// + /// Gets the Interlace exif tag. + /// + public static ExifTag Interlace { get; } = new ExifTag(ExifTagValue.Interlace); + + /// + /// Gets the SelfTimerMode exif tag. + /// + public static ExifTag SelfTimerMode { get; } = new ExifTag(ExifTagValue.SelfTimerMode); + + /// + /// Gets the SensitivityType exif tag. + /// + public static ExifTag SensitivityType { get; } = new ExifTag(ExifTagValue.SensitivityType); + + /// + /// Gets the MeteringMode exif tag. + /// + public static ExifTag MeteringMode { get; } = new ExifTag(ExifTagValue.MeteringMode); + + /// + /// Gets the LightSource exif tag. + /// + public static ExifTag LightSource { get; } = new ExifTag(ExifTagValue.LightSource); + + /// + /// Gets the FocalPlaneResolutionUnit2 exif tag. + /// + public static ExifTag FocalPlaneResolutionUnit2 { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit2); + + /// + /// Gets the SensingMethod2 exif tag. + /// + public static ExifTag SensingMethod2 { get; } = new ExifTag(ExifTagValue.SensingMethod2); + + /// + /// Gets the Flash exif tag. + /// + public static ExifTag Flash { get; } = new ExifTag(ExifTagValue.Flash); + + /// + /// Gets the ColorSpace exif tag. + /// + public static ExifTag ColorSpace { get; } = new ExifTag(ExifTagValue.ColorSpace); + + /// + /// Gets the FocalPlaneResolutionUnit exif tag. + /// + public static ExifTag FocalPlaneResolutionUnit { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit); + + /// + /// Gets the SensingMethod exif tag. + /// + public static ExifTag SensingMethod { get; } = new ExifTag(ExifTagValue.SensingMethod); + + /// + /// Gets the CustomRendered exif tag. + /// + public static ExifTag CustomRendered { get; } = new ExifTag(ExifTagValue.CustomRendered); + + /// + /// Gets the ExposureMode exif tag. + /// + public static ExifTag ExposureMode { get; } = new ExifTag(ExifTagValue.ExposureMode); + + /// + /// Gets the WhiteBalance exif tag. + /// + public static ExifTag WhiteBalance { get; } = new ExifTag(ExifTagValue.WhiteBalance); + + /// + /// Gets the FocalLengthIn35mmFilm exif tag. + /// + public static ExifTag FocalLengthIn35mmFilm { get; } = new ExifTag(ExifTagValue.FocalLengthIn35mmFilm); + + /// + /// Gets the SceneCaptureType exif tag. + /// + public static ExifTag SceneCaptureType { get; } = new ExifTag(ExifTagValue.SceneCaptureType); + + /// + /// Gets the GainControl exif tag. + /// + public static ExifTag GainControl { get; } = new ExifTag(ExifTagValue.GainControl); + + /// + /// Gets the Contrast exif tag. + /// + public static ExifTag Contrast { get; } = new ExifTag(ExifTagValue.Contrast); + + /// + /// Gets the Saturation exif tag. + /// + public static ExifTag Saturation { get; } = new ExifTag(ExifTagValue.Saturation); + + /// + /// Gets the Sharpness exif tag. + /// + public static ExifTag Sharpness { get; } = new ExifTag(ExifTagValue.Sharpness); + + /// + /// Gets the SubjectDistanceRange exif tag. + /// + public static ExifTag SubjectDistanceRange { get; } = new ExifTag(ExifTagValue.SubjectDistanceRange); + + /// + /// Gets the GPSDifferential exif tag. + /// + public static ExifTag GPSDifferential { get; } = new ExifTag(ExifTagValue.GPSDifferential); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs new file mode 100644 index 0000000000..90485f75ab --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the BitsPerSample exif tag. + /// + public static ExifTag BitsPerSample { get; } = new ExifTag(ExifTagValue.BitsPerSample); + + /// + /// Gets the MinSampleValue exif tag. + /// + public static ExifTag MinSampleValue { get; } = new ExifTag(ExifTagValue.MinSampleValue); + + /// + /// Gets the MaxSampleValue exif tag. + /// + public static ExifTag MaxSampleValue { get; } = new ExifTag(ExifTagValue.MaxSampleValue); + + /// + /// Gets the GrayResponseCurve exif tag. + /// + public static ExifTag GrayResponseCurve { get; } = new ExifTag(ExifTagValue.GrayResponseCurve); + + /// + /// Gets the ColorMap exif tag. + /// + public static ExifTag ColorMap { get; } = new ExifTag(ExifTagValue.ColorMap); + + /// + /// Gets the ExtraSamples exif tag. + /// + public static ExifTag ExtraSamples { get; } = new ExifTag(ExifTagValue.ExtraSamples); + + /// + /// Gets the PageNumber exif tag. + /// + public static ExifTag PageNumber { get; } = new ExifTag(ExifTagValue.PageNumber); + + /// + /// Gets the TransferFunction exif tag. + /// + public static ExifTag TransferFunction { get; } = new ExifTag(ExifTagValue.TransferFunction); + + /// + /// Gets the Predictor exif tag. + /// + public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); + + /// + /// Gets the HalftoneHints exif tag. + /// + public static ExifTag HalftoneHints { get; } = new ExifTag(ExifTagValue.HalftoneHints); + + /// + /// Gets the SampleFormat exif tag. + /// + public static ExifTag SampleFormat { get; } = new ExifTag(ExifTagValue.SampleFormat); + + /// + /// Gets the TransferRange exif tag. + /// + public static ExifTag TransferRange { get; } = new ExifTag(ExifTagValue.TransferRange); + + /// + /// Gets the DefaultImageColor exif tag. + /// + public static ExifTag DefaultImageColor { get; } = new ExifTag(ExifTagValue.DefaultImageColor); + + /// + /// Gets the JPEGLosslessPredictors exif tag. + /// + public static ExifTag JPEGLosslessPredictors { get; } = new ExifTag(ExifTagValue.JPEGLosslessPredictors); + + /// + /// Gets the JPEGPointTransforms exif tag. + /// + public static ExifTag JPEGPointTransforms { get; } = new ExifTag(ExifTagValue.JPEGPointTransforms); + + /// + /// Gets the YCbCrSubsampling exif tag. + /// + public static ExifTag YCbCrSubsampling { get; } = new ExifTag(ExifTagValue.YCbCrSubsampling); + + /// + /// Gets the CFARepeatPatternDim exif tag. + /// + public static ExifTag CFARepeatPatternDim { get; } = new ExifTag(ExifTagValue.CFARepeatPatternDim); + + /// + /// Gets the IntergraphPacketData exif tag. + /// + public static ExifTag IntergraphPacketData { get; } = new ExifTag(ExifTagValue.IntergraphPacketData); + + /// + /// Gets the ISOSpeedRatings exif tag. + /// + public static ExifTag ISOSpeedRatings { get; } = new ExifTag(ExifTagValue.ISOSpeedRatings); + + /// + /// Gets the SubjectArea exif tag. + /// + public static ExifTag SubjectArea { get; } = new ExifTag(ExifTagValue.SubjectArea); + + /// + /// Gets the SubjectLocation exif tag. + /// + public static ExifTag SubjectLocation { get; } = new ExifTag(ExifTagValue.SubjectLocation); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs new file mode 100644 index 0000000000..29d61db88c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ShutterSpeedValue exif tag. + /// + public static ExifTag ShutterSpeedValue { get; } = new ExifTag(ExifTagValue.ShutterSpeedValue); + + /// + /// Gets the BrightnessValue exif tag. + /// + public static ExifTag BrightnessValue { get; } = new ExifTag(ExifTagValue.BrightnessValue); + + /// + /// Gets the ExposureBiasValue exif tag. + /// + public static ExifTag ExposureBiasValue { get; } = new ExifTag(ExifTagValue.ExposureBiasValue); + + /// + /// Gets the AmbientTemperature exif tag. + /// + public static ExifTag AmbientTemperature { get; } = new ExifTag(ExifTagValue.AmbientTemperature); + + /// + /// Gets the WaterDepth exif tag. + /// + public static ExifTag WaterDepth { get; } = new ExifTag(ExifTagValue.WaterDepth); + + /// + /// Gets the CameraElevationAngle exif tag. + /// + public static ExifTag CameraElevationAngle { get; } = new ExifTag(ExifTagValue.CameraElevationAngle); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs new file mode 100644 index 0000000000..9a6e3063b0 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the Decode exif tag. + /// + public static ExifTag Decode { get; } = new ExifTag(ExifTagValue.Decode); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs new file mode 100644 index 0000000000..506f874548 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs @@ -0,0 +1,279 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the ImageDescription exif tag. + /// + public static ExifTag ImageDescription { get; } = new ExifTag(ExifTagValue.ImageDescription); + + /// + /// Gets the Make exif tag. + /// + public static ExifTag Make { get; } = new ExifTag(ExifTagValue.Make); + + /// + /// Gets the Model exif tag. + /// + public static ExifTag Model { get; } = new ExifTag(ExifTagValue.Model); + + /// + /// Gets the Software exif tag. + /// + public static ExifTag Software { get; } = new ExifTag(ExifTagValue.Software); + + /// + /// Gets the DateTime exif tag. + /// + public static ExifTag DateTime { get; } = new ExifTag(ExifTagValue.DateTime); + + /// + /// Gets the Artist exif tag. + /// + public static ExifTag Artist { get; } = new ExifTag(ExifTagValue.Artist); + + /// + /// Gets the HostComputer exif tag. + /// + public static ExifTag HostComputer { get; } = new ExifTag(ExifTagValue.HostComputer); + + /// + /// Gets the Copyright exif tag. + /// + public static ExifTag Copyright { get; } = new ExifTag(ExifTagValue.Copyright); + + /// + /// Gets the DocumentName exif tag. + /// + public static ExifTag DocumentName { get; } = new ExifTag(ExifTagValue.DocumentName); + + /// + /// Gets the PageName exif tag. + /// + public static ExifTag PageName { get; } = new ExifTag(ExifTagValue.PageName); + + /// + /// Gets the InkNames exif tag. + /// + public static ExifTag InkNames { get; } = new ExifTag(ExifTagValue.InkNames); + + /// + /// Gets the TargetPrinter exif tag. + /// + public static ExifTag TargetPrinter { get; } = new ExifTag(ExifTagValue.TargetPrinter); + + /// + /// Gets the ImageID exif tag. + /// + public static ExifTag ImageID { get; } = new ExifTag(ExifTagValue.ImageID); + + /// + /// Gets the MDLabName exif tag. + /// + public static ExifTag MDLabName { get; } = new ExifTag(ExifTagValue.MDLabName); + + /// + /// Gets the MDSampleInfo exif tag. + /// + public static ExifTag MDSampleInfo { get; } = new ExifTag(ExifTagValue.MDSampleInfo); + + /// + /// Gets the MDPrepDate exif tag. + /// + public static ExifTag MDPrepDate { get; } = new ExifTag(ExifTagValue.MDPrepDate); + + /// + /// Gets the MDPrepTime exif tag. + /// + public static ExifTag MDPrepTime { get; } = new ExifTag(ExifTagValue.MDPrepTime); + + /// + /// Gets the MDFileUnits exif tag. + /// + public static ExifTag MDFileUnits => new ExifTag(ExifTagValue.MDFileUnits); + + /// + /// Gets the SEMInfo exif tag. + /// + public static ExifTag SEMInfo { get; } = new ExifTag(ExifTagValue.SEMInfo); + + /// + /// Gets the SpectralSensitivity exif tag. + /// + public static ExifTag SpectralSensitivity { get; } = new ExifTag(ExifTagValue.SpectralSensitivity); + + /// + /// Gets the DateTimeOriginal exif tag. + /// + public static ExifTag DateTimeOriginal { get; } = new ExifTag(ExifTagValue.DateTimeOriginal); + + /// + /// Gets the DateTimeDigitized exif tag. + /// + public static ExifTag DateTimeDigitized { get; } = new ExifTag(ExifTagValue.DateTimeDigitized); + + /// + /// Gets the SubsecTime exif tag. + /// + public static ExifTag SubsecTime { get; } = new ExifTag(ExifTagValue.SubsecTime); + + /// + /// Gets the SubsecTimeOriginal exif tag. + /// + public static ExifTag SubsecTimeOriginal { get; } = new ExifTag(ExifTagValue.SubsecTimeOriginal); + + /// + /// Gets the SubsecTimeDigitized exif tag. + /// + public static ExifTag SubsecTimeDigitized { get; } = new ExifTag(ExifTagValue.SubsecTimeDigitized); + + /// + /// Gets the RelatedSoundFile exif tag. + /// + public static ExifTag RelatedSoundFile { get; } = new ExifTag(ExifTagValue.RelatedSoundFile); + + /// + /// Gets the FaxSubaddress exif tag. + /// + public static ExifTag FaxSubaddress { get; } = new ExifTag(ExifTagValue.FaxSubaddress); + + /// + /// Gets the OffsetTime exif tag. + /// + public static ExifTag OffsetTime { get; } = new ExifTag(ExifTagValue.OffsetTime); + + /// + /// Gets the OffsetTimeOriginal exif tag. + /// + public static ExifTag OffsetTimeOriginal { get; } = new ExifTag(ExifTagValue.OffsetTimeOriginal); + + /// + /// Gets the OffsetTimeDigitized exif tag. + /// + public static ExifTag OffsetTimeDigitized { get; } = new ExifTag(ExifTagValue.OffsetTimeDigitized); + + /// + /// Gets the SecurityClassification exif tag. + /// + public static ExifTag SecurityClassification { get; } = new ExifTag(ExifTagValue.SecurityClassification); + + /// + /// Gets the ImageHistory exif tag. + /// + public static ExifTag ImageHistory { get; } = new ExifTag(ExifTagValue.ImageHistory); + + /// + /// Gets the ImageUniqueID exif tag. + /// + public static ExifTag ImageUniqueID { get; } = new ExifTag(ExifTagValue.ImageUniqueID); + + /// + /// Gets the OwnerName exif tag. + /// + public static ExifTag OwnerName { get; } = new ExifTag(ExifTagValue.OwnerName); + + /// + /// Gets the SerialNumber exif tag. + /// + public static ExifTag SerialNumber { get; } = new ExifTag(ExifTagValue.SerialNumber); + + /// + /// Gets the LensMake exif tag. + /// + public static ExifTag LensMake { get; } = new ExifTag(ExifTagValue.LensMake); + + /// + /// Gets the LensModel exif tag. + /// + public static ExifTag LensModel { get; } = new ExifTag(ExifTagValue.LensModel); + + /// + /// Gets the LensSerialNumber exif tag. + /// + public static ExifTag LensSerialNumber { get; } = new ExifTag(ExifTagValue.LensSerialNumber); + + /// + /// Gets the GDALMetadata exif tag. + /// + public static ExifTag GDALMetadata { get; } = new ExifTag(ExifTagValue.GDALMetadata); + + /// + /// Gets the GDALNoData exif tag. + /// + public static ExifTag GDALNoData { get; } = new ExifTag(ExifTagValue.GDALNoData); + + /// + /// Gets the GPSLatitudeRef exif tag. + /// + public static ExifTag GPSLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSLatitudeRef); + + /// + /// Gets the GPSLongitudeRef exif tag. + /// + public static ExifTag GPSLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSLongitudeRef); + + /// + /// Gets the GPSSatellites exif tag. + /// + public static ExifTag GPSSatellites { get; } = new ExifTag(ExifTagValue.GPSSatellites); + + /// + /// Gets the GPSStatus exif tag. + /// + public static ExifTag GPSStatus { get; } = new ExifTag(ExifTagValue.GPSStatus); + + /// + /// Gets the GPSMeasureMode exif tag. + /// + public static ExifTag GPSMeasureMode { get; } = new ExifTag(ExifTagValue.GPSMeasureMode); + + /// + /// Gets the GPSSpeedRef exif tag. + /// + public static ExifTag GPSSpeedRef { get; } = new ExifTag(ExifTagValue.GPSSpeedRef); + + /// + /// Gets the GPSTrackRef exif tag. + /// + public static ExifTag GPSTrackRef { get; } = new ExifTag(ExifTagValue.GPSTrackRef); + + /// + /// Gets the GPSImgDirectionRef exif tag. + /// + public static ExifTag GPSImgDirectionRef { get; } = new ExifTag(ExifTagValue.GPSImgDirectionRef); + + /// + /// Gets the GPSMapDatum exif tag. + /// + public static ExifTag GPSMapDatum { get; } = new ExifTag(ExifTagValue.GPSMapDatum); + + /// + /// Gets the GPSDestLatitudeRef exif tag. + /// + public static ExifTag GPSDestLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLatitudeRef); + + /// + /// Gets the GPSDestLongitudeRef exif tag. + /// + public static ExifTag GPSDestLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLongitudeRef); + + /// + /// Gets the GPSDestBearingRef exif tag. + /// + public static ExifTag GPSDestBearingRef { get; } = new ExifTag(ExifTagValue.GPSDestBearingRef); + + /// + /// Gets the GPSDestDistanceRef exif tag. + /// + public static ExifTag GPSDestDistanceRef { get; } = new ExifTag(ExifTagValue.GPSDestDistanceRef); + + /// + /// Gets the GPSDateStamp exif tag. + /// + public static ExifTag GPSDateStamp { get; } = new ExifTag(ExifTagValue.GPSDateStamp); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs new file mode 100644 index 0000000000..5f48412263 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the JPEGTables exif tag. + /// + public static ExifTag JPEGTables { get; } = new ExifTag(ExifTagValue.JPEGTables); + + /// + /// Gets the OECF exif tag. + /// + public static ExifTag OECF { get; } = new ExifTag(ExifTagValue.OECF); + + /// + /// Gets the ExifVersion exif tag. + /// + public static ExifTag ExifVersion { get; } = new ExifTag(ExifTagValue.ExifVersion); + + /// + /// Gets the ComponentsConfiguration exif tag. + /// + public static ExifTag ComponentsConfiguration { get; } = new ExifTag(ExifTagValue.ComponentsConfiguration); + + /// + /// Gets the MakerNote exif tag. + /// + public static ExifTag MakerNote { get; } = new ExifTag(ExifTagValue.MakerNote); + + /// + /// Gets the UserComment exif tag. + /// + public static ExifTag UserComment { get; } = new ExifTag(ExifTagValue.UserComment); + + /// + /// Gets the FlashpixVersion exif tag. + /// + public static ExifTag FlashpixVersion { get; } = new ExifTag(ExifTagValue.FlashpixVersion); + + /// + /// Gets the SpatialFrequencyResponse exif tag. + /// + public static ExifTag SpatialFrequencyResponse { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse); + + /// + /// Gets the SpatialFrequencyResponse2 exif tag. + /// + public static ExifTag SpatialFrequencyResponse2 { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse2); + + /// + /// Gets the Noise exif tag. + /// + public static ExifTag Noise { get; } = new ExifTag(ExifTagValue.Noise); + + /// + /// Gets the CFAPattern exif tag. + /// + public static ExifTag CFAPattern { get; } = new ExifTag(ExifTagValue.CFAPattern); + + /// + /// Gets the DeviceSettingDescription exif tag. + /// + public static ExifTag DeviceSettingDescription { get; } = new ExifTag(ExifTagValue.DeviceSettingDescription); + + /// + /// Gets the ImageSourceData exif tag. + /// + public static ExifTag ImageSourceData { get; } = new ExifTag(ExifTagValue.ImageSourceData); + + /// + /// Gets the GPSProcessingMethod exif tag. + /// + public static ExifTag GPSProcessingMethod { get; } = new ExifTag(ExifTagValue.GPSProcessingMethod); + + /// + /// Gets the GPSAreaInformation exif tag. + /// + public static ExifTag GPSAreaInformation { get; } = new ExifTag(ExifTagValue.GPSAreaInformation); + + /// + /// Gets the FileSource exif tag. + /// + public static ExifTag FileSource { get; } = new ExifTag(ExifTagValue.FileSource); + + /// + /// Gets the ImageDescription exif tag. + /// + public static ExifTag SceneType { get; } = new ExifTag(ExifTagValue.SceneType); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs new file mode 100644 index 0000000000..65e0314626 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Class that represents an exif tag from the Exif standard 2.31. + /// + public abstract partial class ExifTag : IEquatable + { + private readonly ushort value; + + internal ExifTag(ushort value) => this.value = value; + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator ushort(ExifTag tag) => tag?.value ?? (ushort)ExifTagValue.Unknown; + + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(ExifTag left, ExifTag right) => Equals(left, right); + + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(ExifTag left, ExifTag right) => !Equals(left, right); + + /// + public override bool Equals(object obj) + { + if (obj is ExifTag value) + { + return this.Equals(value); + } + + return false; + } + + /// + public bool Equals(ExifTag other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.value == other.value; + } + + /// + public override int GetHashCode() => this.value.GetHashCode(); + + /// + public override string ToString() => ((ExifTagValue)this.value).ToString(); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs new file mode 100644 index 0000000000..f70bcea37a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs @@ -0,0 +1,1543 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// All exif tags from the Exif standard 2.31. + /// + internal enum ExifTagValue + { + /// + /// Unknown + /// + Unknown = 0xFFFF, + + /// + /// SubIFDOffset + /// + SubIFDOffset = 0x8769, + + /// + /// GPSIFDOffset + /// + GPSIFDOffset = 0x8825, + + /// + /// SubfileType + /// + [ExifTagDescription(0U, "Full-resolution Image")] + [ExifTagDescription(1U, "Reduced-resolution image")] + [ExifTagDescription(2U, "Single page of multi-page image")] + [ExifTagDescription(3U, "Single page of multi-page reduced-resolution image")] + [ExifTagDescription(4U, "Transparency mask")] + [ExifTagDescription(5U, "Transparency mask of reduced-resolution image")] + [ExifTagDescription(6U, "Transparency mask of multi-page image")] + [ExifTagDescription(7U, "Transparency mask of reduced-resolution multi-page image")] + [ExifTagDescription(0x10001U, "Alternate reduced-resolution image ")] + SubfileType = 0x00FE, + + /// + /// OldSubfileType + /// + [ExifTagDescription((ushort)1, "Full-resolution Image")] + [ExifTagDescription((ushort)2, "Reduced-resolution image")] + [ExifTagDescription((ushort)3, "Single page of multi-page image")] + OldSubfileType = 0x00FF, + + /// + /// ImageWidth + /// + ImageWidth = 0x0100, + + /// + /// ImageLength + /// + ImageLength = 0x0101, + + /// + /// BitsPerSample + /// + BitsPerSample = 0x0102, + + /// + /// Compression + /// + [ExifTagDescription((ushort)1, "Uncompressed")] + [ExifTagDescription((ushort)2, "CCITT 1D")] + [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] + [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] + [ExifTagDescription((ushort)5, "LZW")] + [ExifTagDescription((ushort)6, "JPEG (old-style)")] + [ExifTagDescription((ushort)7, "JPEG")] + [ExifTagDescription((ushort)8, "Adobe Deflate")] + [ExifTagDescription((ushort)9, "JBIG B&W")] + [ExifTagDescription((ushort)10, "JBIG Color")] + [ExifTagDescription((ushort)99, "JPEG")] + [ExifTagDescription((ushort)262, "Kodak 262")] + [ExifTagDescription((ushort)32766, "Next")] + [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] + [ExifTagDescription((ushort)32769, "Packed RAW")] + [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] + [ExifTagDescription((ushort)32771, "CCIRLEW")] + [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] + [ExifTagDescription((ushort)32773, "PackBits")] + [ExifTagDescription((ushort)32809, "Thunderscan")] + [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] + [ExifTagDescription((ushort)32895, "IT8CTPAD")] + [ExifTagDescription((ushort)32896, "IT8LW")] + [ExifTagDescription((ushort)32897, "IT8MP")] + [ExifTagDescription((ushort)32898, "IT8BL")] + [ExifTagDescription((ushort)32908, "PixarFilm")] + [ExifTagDescription((ushort)32909, "PixarLog")] + [ExifTagDescription((ushort)32946, "Deflate")] + [ExifTagDescription((ushort)32947, "DCS")] + [ExifTagDescription((ushort)34661, "JBIG")] + [ExifTagDescription((ushort)34676, "SGILog")] + [ExifTagDescription((ushort)34677, "SGILog24")] + [ExifTagDescription((ushort)34712, "JPEG 2000")] + [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] + [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] + [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] + [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] + [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] + [ExifTagDescription((ushort)34892, "Lossy JPEG")] + [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] + [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] + Compression = 0x0103, + + /// + /// PhotometricInterpretation + /// + [ExifTagDescription((ushort)0, "WhiteIsZero")] + [ExifTagDescription((ushort)1, "BlackIsZero")] + [ExifTagDescription((ushort)2, "RGB")] + [ExifTagDescription((ushort)3, "RGB Palette")] + [ExifTagDescription((ushort)4, "Transparency Mask")] + [ExifTagDescription((ushort)5, "CMYK")] + [ExifTagDescription((ushort)6, "YCbCr")] + [ExifTagDescription((ushort)8, "CIELab")] + [ExifTagDescription((ushort)9, "ICCLab")] + [ExifTagDescription((ushort)10, "TULab")] + [ExifTagDescription((ushort)32803, "Color Filter Array")] + [ExifTagDescription((ushort)32844, "Pixar LogL")] + [ExifTagDescription((ushort)32845, "Pixar LogLuv")] + [ExifTagDescription((ushort)34892, "Linear Raw")] + PhotometricInterpretation = 0x0106, + + /// + /// Thresholding + /// + [ExifTagDescription((ushort)1, "No dithering or halftoning")] + [ExifTagDescription((ushort)2, "Ordered dither or halftone")] + [ExifTagDescription((ushort)3, "Randomized dither")] + Thresholding = 0x0107, + + /// + /// CellWidth + /// + CellWidth = 0x0108, + + /// + /// CellLength + /// + CellLength = 0x0109, + + /// + /// FillOrder + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Reversed")] + FillOrder = 0x010A, + + /// + /// DocumentName + /// + DocumentName = 0x010D, + + /// + /// ImageDescription + /// + ImageDescription = 0x010E, + + /// + /// Make + /// + Make = 0x010F, + + /// + /// Model + /// + Model = 0x0110, + + /// + /// StripOffsets + /// + StripOffsets = 0x0111, + + /// + /// Orientation + /// + [ExifTagDescription((ushort)1, "Horizontal (normal)")] + [ExifTagDescription((ushort)2, "Mirror horizontal")] + [ExifTagDescription((ushort)3, "Rotate 180")] + [ExifTagDescription((ushort)4, "Mirror vertical")] + [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] + [ExifTagDescription((ushort)6, "Rotate 90 CW")] + [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] + [ExifTagDescription((ushort)8, "Rotate 270 CW")] + Orientation = 0x0112, + + /// + /// SamplesPerPixel + /// + SamplesPerPixel = 0x0115, + + /// + /// RowsPerStrip + /// + RowsPerStrip = 0x0116, + + /// + /// StripByteCounts + /// + StripByteCounts = 0x0117, + + /// + /// MinSampleValue + /// + MinSampleValue = 0x0118, + + /// + /// MaxSampleValue + /// + MaxSampleValue = 0x0119, + + /// + /// XResolution + /// + XResolution = 0x011A, + + /// + /// YResolution + /// + YResolution = 0x011B, + + /// + /// PlanarConfiguration + /// + [ExifTagDescription((ushort)1, "Chunky")] + [ExifTagDescription((ushort)2, "Planar")] + PlanarConfiguration = 0x011C, + + /// + /// PageName + /// + PageName = 0x011D, + + /// + /// XPosition + /// + XPosition = 0x011E, + + /// + /// YPosition + /// + YPosition = 0x011F, + + /// + /// FreeOffsets + /// + FreeOffsets = 0x0120, + + /// + /// FreeByteCounts + /// + FreeByteCounts = 0x0121, + + /// + /// GrayResponseUnit + /// + [ExifTagDescription((ushort)1, "0.1")] + [ExifTagDescription((ushort)2, "0.001")] + [ExifTagDescription((ushort)3, "0.0001")] + [ExifTagDescription((ushort)4, "1e-05")] + [ExifTagDescription((ushort)5, "1e-06")] + GrayResponseUnit = 0x0122, + + /// + /// GrayResponseCurve + /// + GrayResponseCurve = 0x0123, + + /// + /// T4Options + /// + [ExifTagDescription(0U, "2-Dimensional encoding")] + [ExifTagDescription(1U, "Uncompressed")] + [ExifTagDescription(2U, "Fill bits added")] + T4Options = 0x0124, + + /// + /// T6Options + /// + [ExifTagDescription(1U, "Uncompressed")] + T6Options = 0x0125, + + /// + /// ResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + ResolutionUnit = 0x0128, + + /// + /// PageNumber + /// + PageNumber = 0x0129, + + /// + /// ColorResponseUnit + /// + ColorResponseUnit = 0x012C, + + /// + /// TransferFunction + /// + TransferFunction = 0x012D, + + /// + /// Software + /// + Software = 0x0131, + + /// + /// DateTime + /// + DateTime = 0x0132, + + /// + /// Artist + /// + Artist = 0x013B, + + /// + /// HostComputer + /// + HostComputer = 0x013C, + + /// + /// Predictor + /// + Predictor = 0x013D, + + /// + /// WhitePoint + /// + WhitePoint = 0x013E, + + /// + /// PrimaryChromaticities + /// + PrimaryChromaticities = 0x013F, + + /// + /// ColorMap + /// + ColorMap = 0x0140, + + /// + /// HalftoneHints + /// + HalftoneHints = 0x0141, + + /// + /// TileWidth + /// + TileWidth = 0x0142, + + /// + /// TileLength + /// + TileLength = 0x0143, + + /// + /// TileOffsets + /// + TileOffsets = 0x0144, + + /// + /// TileByteCounts + /// + TileByteCounts = 0x0145, + + /// + /// BadFaxLines + /// + BadFaxLines = 0x0146, + + /// + /// CleanFaxData + /// + [ExifTagDescription(0U, "Clean")] + [ExifTagDescription(1U, "Regenerated")] + [ExifTagDescription(2U, "Unclean")] + CleanFaxData = 0x0147, + + /// + /// ConsecutiveBadFaxLines + /// + ConsecutiveBadFaxLines = 0x0148, + + /// + /// InkSet + /// + [ExifTagDescription((ushort)1, "CMYK")] + [ExifTagDescription((ushort)2, "Not CMYK")] + InkSet = 0x014C, + + /// + /// InkNames + /// + InkNames = 0x014D, + + /// + /// NumberOfInks + /// + NumberOfInks = 0x014E, + + /// + /// DotRange + /// + DotRange = 0x0150, + + /// + /// TargetPrinter + /// + TargetPrinter = 0x0151, + + /// + /// ExtraSamples + /// + [ExifTagDescription((ushort)0, "Unspecified")] + [ExifTagDescription((ushort)1, "Associated Alpha")] + [ExifTagDescription((ushort)2, "Unassociated Alpha")] + ExtraSamples = 0x0152, + + /// + /// SampleFormat + /// + [ExifTagDescription((ushort)1, "Unsigned")] + [ExifTagDescription((ushort)2, "Signed")] + [ExifTagDescription((ushort)3, "Float")] + [ExifTagDescription((ushort)4, "Undefined")] + [ExifTagDescription((ushort)5, "Complex int")] + [ExifTagDescription((ushort)6, "Complex float")] + SampleFormat = 0x0153, + + /// + /// SMinSampleValue + /// + SMinSampleValue = 0x0154, + + /// + /// SMaxSampleValue + /// + SMaxSampleValue = 0x0155, + + /// + /// TransferRange + /// + TransferRange = 0x0156, + + /// + /// ClipPath + /// + ClipPath = 0x0157, + + /// + /// XClipPathUnits + /// + XClipPathUnits = 0x0158, + + /// + /// YClipPathUnits + /// + YClipPathUnits = 0x0159, + + /// + /// Indexed + /// + [ExifTagDescription((ushort)0, "Not indexed")] + [ExifTagDescription((ushort)1, "Indexed")] + Indexed = 0x015A, + + /// + /// JPEGTables + /// + JPEGTables = 0x015B, + + /// + /// OPIProxy + /// + [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] + [ExifTagDescription((ushort)1, "Higher resolution image exists")] + OPIProxy = 0x015F, + + /// + /// ProfileType + /// + [ExifTagDescription(0U, "Unspecified")] + [ExifTagDescription(1U, "Group 3 FAX")] + ProfileType = 0x0191, + + /// + /// FaxProfile + /// + [ExifTagDescription((byte)0, "Unknown")] + [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] + [ExifTagDescription((byte)2, "Extended B&W lossless, F")] + [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] + [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] + [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] + [ExifTagDescription((byte)6, "Mixed raster content, M")] + [ExifTagDescription((byte)7, "Profile T")] + [ExifTagDescription((byte)255, "Multi Profiles")] + FaxProfile = 0x0192, + + /// + /// CodingMethods + /// + [ExifTagDescription(0UL, "Unspecified compression")] + [ExifTagDescription(1UL, "Modified Huffman")] + [ExifTagDescription(2UL, "Modified Read")] + [ExifTagDescription(4UL, "Modified MR")] + [ExifTagDescription(8UL, "JBIG")] + [ExifTagDescription(16UL, "Baseline JPEG")] + [ExifTagDescription(32UL, "JBIG color")] + CodingMethods = 0x0193, + + /// + /// VersionYear + /// + VersionYear = 0x0194, + + /// + /// ModeNumber + /// + ModeNumber = 0x0195, + + /// + /// Decode + /// + Decode = 0x01B1, + + /// + /// DefaultImageColor + /// + DefaultImageColor = 0x01B2, + + /// + /// T82ptions + /// + T82ptions = 0x01B3, + + /// + /// JPEGProc + /// + [ExifTagDescription((ushort)1, "Baseline")] + [ExifTagDescription((ushort)14, "Lossless")] + JPEGProc = 0x0200, + + /// + /// JPEGInterchangeFormat + /// + JPEGInterchangeFormat = 0x0201, + + /// + /// JPEGInterchangeFormatLength + /// + JPEGInterchangeFormatLength = 0x0202, + + /// + /// JPEGRestartInterval + /// + JPEGRestartInterval = 0x0203, + + /// + /// JPEGLosslessPredictors + /// + JPEGLosslessPredictors = 0x0205, + + /// + /// JPEGPointTransforms + /// + JPEGPointTransforms = 0x0206, + + /// + /// JPEGQTables + /// + JPEGQTables = 0x0207, + + /// + /// JPEGDCTables + /// + JPEGDCTables = 0x0208, + + /// + /// JPEGACTables + /// + JPEGACTables = 0x0209, + + /// + /// YCbCrCoefficients + /// + YCbCrCoefficients = 0x0211, + + /// + /// YCbCrSubsampling + /// + YCbCrSubsampling = 0x0212, + + /// + /// YCbCrPositioning + /// + [ExifTagDescription((ushort)1, "Centered")] + [ExifTagDescription((ushort)2, "Co-sited")] + YCbCrPositioning = 0x0213, + + /// + /// ReferenceBlackWhite + /// + ReferenceBlackWhite = 0x0214, + + /// + /// StripRowCounts + /// + StripRowCounts = 0x022F, + + /// + /// XMP + /// + XMP = 0x02BC, + + /// + /// Rating + /// + Rating = 0x4746, + + /// + /// RatingPercent + /// + RatingPercent = 0x4749, + + /// + /// ImageID + /// + ImageID = 0x800D, + + /// + /// CFARepeatPatternDim + /// + CFARepeatPatternDim = 0x828D, + + /// + /// CFAPattern2 + /// + CFAPattern2 = 0x828E, + + /// + /// BatteryLevel + /// + BatteryLevel = 0x828F, + + /// + /// Copyright + /// + Copyright = 0x8298, + + /// + /// ExposureTime + /// + ExposureTime = 0x829A, + + /// + /// FNumber + /// + FNumber = 0x829D, + + /// + /// MDFileTag + /// + MDFileTag = 0x82A5, + + /// + /// MDScalePixel + /// + MDScalePixel = 0x82A6, + + /// + /// MDLabName + /// + MDLabName = 0x82A8, + + /// + /// MDSampleInfo + /// + MDSampleInfo = 0x82A9, + + /// + /// MDPrepDate + /// + MDPrepDate = 0x82AA, + + /// + /// MDPrepTime + /// + MDPrepTime = 0x82AB, + + /// + /// MDFileUnits + /// + MDFileUnits = 0x82AC, + + /// + /// PixelScale + /// + PixelScale = 0x830E, + + /// + /// IntergraphPacketData + /// + IntergraphPacketData = 0x847E, + + /// + /// IntergraphRegisters + /// + IntergraphRegisters = 0x847F, + + /// + /// IntergraphMatrix + /// + IntergraphMatrix = 0x8480, + + /// + /// ModelTiePoint + /// + ModelTiePoint = 0x8482, + + /// + /// SEMInfo + /// + SEMInfo = 0x8546, + + /// + /// ModelTransform + /// + ModelTransform = 0x85D8, + + /// + /// ImageLayer + /// + ImageLayer = 0x87AC, + + /// + /// ExposureProgram + /// + [ExifTagDescription((ushort)0, "Not Defined")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Program AE")] + [ExifTagDescription((ushort)3, "Aperture-priority AE")] + [ExifTagDescription((ushort)4, "Shutter speed priority AE")] + [ExifTagDescription((ushort)5, "Creative (Slow speed)")] + [ExifTagDescription((ushort)6, "Action (High speed)")] + [ExifTagDescription((ushort)7, "Portrait")] + [ExifTagDescription((ushort)8, "Landscape")] + [ExifTagDescription((ushort)9, "Bulb")] + ExposureProgram = 0x8822, + + /// + /// SpectralSensitivity + /// + SpectralSensitivity = 0x8824, + + /// + /// ISOSpeedRatings + /// + ISOSpeedRatings = 0x8827, + + /// + /// OECF + /// + OECF = 0x8828, + + /// + /// Interlace + /// + Interlace = 0x8829, + + /// + /// TimeZoneOffset + /// + TimeZoneOffset = 0x882A, + + /// + /// SelfTimerMode + /// + SelfTimerMode = 0x882B, + + /// + /// SensitivityType + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] + [ExifTagDescription((ushort)2, "Recommended Exposure Index")] + [ExifTagDescription((ushort)3, "ISO Speed")] + [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] + [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] + [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] + [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] + SensitivityType = 0x8830, + + /// + /// StandardOutputSensitivity + /// + StandardOutputSensitivity = 0x8831, + + /// + /// RecommendedExposureIndex + /// + RecommendedExposureIndex = 0x8832, + + /// + /// ISOSpeed + /// + ISOSpeed = 0x8833, + + /// + /// ISOSpeedLatitudeyyy + /// + ISOSpeedLatitudeyyy = 0x8834, + + /// + /// ISOSpeedLatitudezzz + /// + ISOSpeedLatitudezzz = 0x8835, + + /// + /// FaxRecvParams + /// + FaxRecvParams = 0x885C, + + /// + /// FaxSubaddress + /// + FaxSubaddress = 0x885D, + + /// + /// FaxRecvTime + /// + FaxRecvTime = 0x885E, + + /// + /// ExifVersion + /// + ExifVersion = 0x9000, + + /// + /// DateTimeOriginal + /// + DateTimeOriginal = 0x9003, + + /// + /// DateTimeDigitized + /// + DateTimeDigitized = 0x9004, + + /// + /// OffsetTime + /// + OffsetTime = 0x9010, + + /// + /// OffsetTimeOriginal + /// + OffsetTimeOriginal = 0x9011, + + /// + /// OffsetTimeDigitized + /// + OffsetTimeDigitized = 0x9012, + + /// + /// ComponentsConfiguration + /// + ComponentsConfiguration = 0x9101, + + /// + /// CompressedBitsPerPixel + /// + CompressedBitsPerPixel = 0x9102, + + /// + /// ShutterSpeedValue + /// + ShutterSpeedValue = 0x9201, + + /// + /// ApertureValue + /// + ApertureValue = 0x9202, + + /// + /// BrightnessValue + /// + BrightnessValue = 0x9203, + + /// + /// ExposureBiasValue + /// + ExposureBiasValue = 0x9204, + + /// + /// MaxApertureValue + /// + MaxApertureValue = 0x9205, + + /// + /// SubjectDistance + /// + SubjectDistance = 0x9206, + + /// + /// MeteringMode + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Average")] + [ExifTagDescription((ushort)2, "Center-weighted average")] + [ExifTagDescription((ushort)3, "Spot")] + [ExifTagDescription((ushort)4, "Multi-spot")] + [ExifTagDescription((ushort)5, "Multi-segment")] + [ExifTagDescription((ushort)6, "Partial")] + [ExifTagDescription((ushort)255, "Other")] + MeteringMode = 0x9207, + + /// + /// LightSource + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Daylight")] + [ExifTagDescription((ushort)2, "Fluorescent")] + [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] + [ExifTagDescription((ushort)4, "Flash")] + [ExifTagDescription((ushort)9, "Fine Weather")] + [ExifTagDescription((ushort)10, "Cloudy")] + [ExifTagDescription((ushort)11, "Shade")] + [ExifTagDescription((ushort)12, "Daylight Fluorescent")] + [ExifTagDescription((ushort)13, "Day White Fluorescent")] + [ExifTagDescription((ushort)14, "Cool White Fluorescent")] + [ExifTagDescription((ushort)15, "White Fluorescent")] + [ExifTagDescription((ushort)16, "Warm White Fluorescent")] + [ExifTagDescription((ushort)17, "Standard Light A")] + [ExifTagDescription((ushort)18, "Standard Light B")] + [ExifTagDescription((ushort)19, "Standard Light C")] + [ExifTagDescription((ushort)20, "D55")] + [ExifTagDescription((ushort)21, "D65")] + [ExifTagDescription((ushort)22, "D75")] + [ExifTagDescription((ushort)23, "D50")] + [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] + [ExifTagDescription((ushort)255, "Other")] + LightSource = 0x9208, + + /// + /// Flash + /// + [ExifTagDescription((ushort)0, "No Flash")] + [ExifTagDescription((ushort)1, "Fired")] + [ExifTagDescription((ushort)5, "Fired, Return not detected")] + [ExifTagDescription((ushort)7, "Fired, Return detected")] + [ExifTagDescription((ushort)8, "On, Did not fire")] + [ExifTagDescription((ushort)9, "On, Fired")] + [ExifTagDescription((ushort)13, "On, Return not detected")] + [ExifTagDescription((ushort)15, "On, Return detected")] + [ExifTagDescription((ushort)16, "Off, Did not fire")] + [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] + [ExifTagDescription((ushort)24, "Auto, Did not fire")] + [ExifTagDescription((ushort)25, "Auto, Fired")] + [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] + [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] + [ExifTagDescription((ushort)32, "No flash function")] + [ExifTagDescription((ushort)48, "Off, No flash function")] + [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] + [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)73, "On, Red-eye reduction")] + [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)79, "On, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] + [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] + [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] + [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] + Flash = 0x9209, + + /// + /// FocalLength + /// + FocalLength = 0x920A, + + /// + /// FlashEnergy2 + /// + FlashEnergy2 = 0x920B, + + /// + /// SpatialFrequencyResponse2 + /// + SpatialFrequencyResponse2 = 0x920C, + + /// + /// Noise + /// + Noise = 0x920D, + + /// + /// FocalPlaneXResolution2 + /// + FocalPlaneXResolution2 = 0x920E, + + /// + /// FocalPlaneYResolution2 + /// + FocalPlaneYResolution2 = 0x920F, + + /// + /// FocalPlaneResolutionUnit2 + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit2 = 0x9210, + + /// + /// ImageNumber + /// + ImageNumber = 0x9211, + + /// + /// SecurityClassification + /// + [ExifTagDescription("C", "Confidential")] + [ExifTagDescription("R", "Restricted")] + [ExifTagDescription("S", "Secret")] + [ExifTagDescription("T", "Top Secret")] + [ExifTagDescription("U", "Unclassified")] + SecurityClassification = 0x9212, + + /// + /// ImageHistory + /// + ImageHistory = 0x9213, + + /// + /// SubjectArea + /// + SubjectArea = 0x9214, + + /// + /// ExposureIndex2 + /// + ExposureIndex2 = 0x9215, + + /// + /// TIFFEPStandardID + /// + TIFFEPStandardID = 0x9216, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod2 = 0x9217, + + /// + /// MakerNote + /// + MakerNote = 0x927C, + + /// + /// UserComment + /// + UserComment = 0x9286, + + /// + /// SubsecTime + /// + SubsecTime = 0x9290, + + /// + /// SubsecTimeOriginal + /// + SubsecTimeOriginal = 0x9291, + + /// + /// SubsecTimeDigitized + /// + SubsecTimeDigitized = 0x9292, + + /// + /// ImageSourceData + /// + ImageSourceData = 0x935C, + + /// + /// AmbientTemperature + /// + AmbientTemperature = 0x9400, + + /// + /// Humidity + /// + Humidity = 0x9401, + + /// + /// Pressure + /// + Pressure = 0x9402, + + /// + /// WaterDepth + /// + WaterDepth = 0x9403, + + /// + /// Acceleration + /// + Acceleration = 0x9404, + + /// + /// CameraElevationAngle + /// + CameraElevationAngle = 0x9405, + + /// + /// XPTitle + /// + XPTitle = 0x9C9B, + + /// + /// XPComment + /// + XPComment = 0x9C9C, + + /// + /// XPAuthor + /// + XPAuthor = 0x9C9D, + + /// + /// XPKeywords + /// + XPKeywords = 0x9C9E, + + /// + /// XPSubject + /// + XPSubject = 0x9C9F, + + /// + /// FlashpixVersion + /// + FlashpixVersion = 0xA000, + + /// + /// ColorSpace + /// + [ExifTagDescription((ushort)1, "sRGB")] + [ExifTagDescription((ushort)2, "Adobe RGB")] + [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] + [ExifTagDescription((ushort)65534, "ICC Profile")] + [ExifTagDescription((ushort)65535, "Uncalibrated")] + ColorSpace = 0xA001, + + /// + /// PixelXDimension + /// + PixelXDimension = 0xA002, + + /// + /// PixelYDimension + /// + PixelYDimension = 0xA003, + + /// + /// RelatedSoundFile + /// + RelatedSoundFile = 0xA004, + + /// + /// FlashEnergy + /// + FlashEnergy = 0xA20B, + + /// + /// SpatialFrequencyResponse + /// + SpatialFrequencyResponse = 0xA20C, + + /// + /// FocalPlaneXResolution + /// + FocalPlaneXResolution = 0xA20E, + + /// + /// FocalPlaneYResolution + /// + FocalPlaneYResolution = 0xA20F, + + /// + /// FocalPlaneResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit = 0xA210, + + /// + /// SubjectLocation + /// + SubjectLocation = 0xA214, + + /// + /// ExposureIndex + /// + ExposureIndex = 0xA215, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod = 0xA217, + + /// + /// FileSource + /// + FileSource = 0xA300, + + /// + /// SceneType + /// + SceneType = 0xA301, + + /// + /// CFAPattern + /// + CFAPattern = 0xA302, + + /// + /// CustomRendered + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Custom")] + CustomRendered = 0xA401, + + /// + /// ExposureMode + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Auto bracket")] + ExposureMode = 0xA402, + + /// + /// WhiteBalance + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + WhiteBalance = 0xA403, + + /// + /// DigitalZoomRatio + /// + DigitalZoomRatio = 0xA404, + + /// + /// FocalLengthIn35mmFilm + /// + FocalLengthIn35mmFilm = 0xA405, + + /// + /// SceneCaptureType + /// + [ExifTagDescription((ushort)0, "Standard")] + [ExifTagDescription((ushort)1, "Landscape")] + [ExifTagDescription((ushort)2, "Portrait")] + [ExifTagDescription((ushort)3, "Night")] + SceneCaptureType = 0xA406, + + /// + /// GainControl + /// + [ExifTagDescription((ushort)0, "None")] + [ExifTagDescription((ushort)1, "Low gain up")] + [ExifTagDescription((ushort)2, "High gain up")] + [ExifTagDescription((ushort)3, "Low gain down")] + [ExifTagDescription((ushort)4, "High gain down")] + GainControl = 0xA407, + + /// + /// Contrast + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Contrast = 0xA408, + + /// + /// Saturation + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Saturation = 0xA409, + + /// + /// Sharpness + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Soft")] + [ExifTagDescription((ushort)2, "Hard")] + Sharpness = 0xA40A, + + /// + /// DeviceSettingDescription + /// + DeviceSettingDescription = 0xA40B, + + /// + /// SubjectDistanceRange + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Macro")] + [ExifTagDescription((ushort)2, "Close")] + [ExifTagDescription((ushort)3, "Distant")] + SubjectDistanceRange = 0xA40C, + + /// + /// ImageUniqueID + /// + ImageUniqueID = 0xA420, + + /// + /// OwnerName + /// + OwnerName = 0xA430, + + /// + /// SerialNumber + /// + SerialNumber = 0xA431, + + /// + /// LensSpecification + /// + LensSpecification = 0xA432, + + /// + /// LensMake + /// + LensMake = 0xA433, + + /// + /// LensModel + /// + LensModel = 0xA434, + + /// + /// LensSerialNumber + /// + LensSerialNumber = 0xA435, + + /// + /// GDALMetadata + /// + GDALMetadata = 0xA480, + + /// + /// GDALNoData + /// + GDALNoData = 0xA481, + + /// + /// GPSVersionID + /// + GPSVersionID = 0x0000, + + /// + /// GPSLatitudeRef + /// + GPSLatitudeRef = 0x0001, + + /// + /// GPSLatitude + /// + GPSLatitude = 0x0002, + + /// + /// GPSLongitudeRef + /// + GPSLongitudeRef = 0x0003, + + /// + /// GPSLongitude + /// + GPSLongitude = 0x0004, + + /// + /// GPSAltitudeRef + /// + GPSAltitudeRef = 0x0005, + + /// + /// GPSAltitude + /// + GPSAltitude = 0x0006, + + /// + /// GPSTimestamp + /// + GPSTimestamp = 0x0007, + + /// + /// GPSSatellites + /// + GPSSatellites = 0x0008, + + /// + /// GPSStatus + /// + GPSStatus = 0x0009, + + /// + /// GPSMeasureMode + /// + GPSMeasureMode = 0x000A, + + /// + /// GPSDOP + /// + GPSDOP = 0x000B, + + /// + /// GPSSpeedRef + /// + GPSSpeedRef = 0x000C, + + /// + /// GPSSpeed + /// + GPSSpeed = 0x000D, + + /// + /// GPSTrackRef + /// + GPSTrackRef = 0x000E, + + /// + /// GPSTrack + /// + GPSTrack = 0x000F, + + /// + /// GPSImgDirectionRef + /// + GPSImgDirectionRef = 0x0010, + + /// + /// GPSImgDirection + /// + GPSImgDirection = 0x0011, + + /// + /// GPSMapDatum + /// + GPSMapDatum = 0x0012, + + /// + /// GPSDestLatitudeRef + /// + GPSDestLatitudeRef = 0x0013, + + /// + /// GPSDestLatitude + /// + GPSDestLatitude = 0x0014, + + /// + /// GPSDestLongitudeRef + /// + GPSDestLongitudeRef = 0x0015, + + /// + /// GPSDestLongitude + /// + GPSDestLongitude = 0x0016, + + /// + /// GPSDestBearingRef + /// + GPSDestBearingRef = 0x0017, + + /// + /// GPSDestBearing + /// + GPSDestBearing = 0x0018, + + /// + /// GPSDestDistanceRef + /// + GPSDestDistanceRef = 0x0019, + + /// + /// GPSDestDistance + /// + GPSDestDistance = 0x001A, + + /// + /// GPSProcessingMethod + /// + GPSProcessingMethod = 0x001B, + + /// + /// GPSAreaInformation + /// + GPSAreaInformation = 0x001C, + + /// + /// GPSDateStamp + /// + GPSDateStamp = 0x001D, + + /// + /// GPSDifferential + /// + GPSDifferential = 0x001E, + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs new file mode 100644 index 0000000000..5a674277a2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Class that represents an exif tag from the Exif standard 2.31 with as the data type of the tag. + /// + /// The data type of the tag. + public sealed class ExifTag : ExifTag + { + internal ExifTag(ExifTagValue value) + : base((ushort)value) + { + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs new file mode 100644 index 0000000000..8f0b36638c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class UnkownExifTag : ExifTag + { + internal UnkownExifTag(ExifTagValue value) + : base((ushort)value) + { + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs new file mode 100644 index 0000000000..263bf09348 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal abstract class ExifArrayValue : ExifValue, IExifValue + { + protected ExifArrayValue(ExifTag tag) + : base(tag) + { + } + + protected ExifArrayValue(ExifTagValue tag) + : base(tag) + { + } + + internal ExifArrayValue(ExifArrayValue value) + : base(value) + { + } + + public override bool IsArray => true; + + public TValueType[] Value { get; set; } + + public override object GetValue() => this.Value; + + public override bool TrySetValue(object value) + { + if (value is null) + { + this.Value = null; + return true; + } + + Type type = value.GetType(); + if (value.GetType() == typeof(TValueType[])) + { + this.Value = (TValueType[])value; + return true; + } + + if (type == typeof(TValueType)) + { + this.Value = new TValueType[] { (TValueType)value }; + return true; + } + + return false; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs new file mode 100644 index 0000000000..184f4a07c4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifByte : ExifValue + { + public ExifByte(ExifTag tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; + + public ExifByte(ExifTagValue tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; + + private ExifByte(ExifByte value) + : base(value) => this.DataType = value.DataType; + + public override ExifDataType DataType { get; } + + protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + if (intValue >= byte.MinValue && intValue <= byte.MaxValue) + { + this.Value = (byte)intValue; + return true; + } + + return false; + default: + return base.TrySetValue(value); + } + } + + public override IExifValue DeepClone() => new ExifByte(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs new file mode 100644 index 0000000000..854eafc767 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifByteArray : ExifArrayValue + { + public ExifByteArray(ExifTag tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; + + public ExifByteArray(ExifTagValue tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; + + private ExifByteArray(ExifByteArray value) + : base(value) => this.DataType = value.DataType; + + public override ExifDataType DataType { get; } + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + if (value is int[] intArrayValue) + { + return this.TrySetSignedIntArray(intArrayValue); + } + + if (value is int intValue) + { + if (intValue >= byte.MinValue && intValue <= byte.MaxValue) + { + this.Value = new byte[] { (byte)intValue }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifByteArray(this); + + private bool TrySetSignedIntArray(int[] intArrayValue) + { + if (Array.FindIndex(intArrayValue, x => x < byte.MinValue || x > byte.MaxValue) > -1) + { + return false; + } + + var value = new byte[intArrayValue.Length]; + for (int i = 0; i < intArrayValue.Length; i++) + { + int s = intArrayValue[i]; + value[i] = (byte)s; + } + + this.Value = value; + return true; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs new file mode 100644 index 0000000000..0af5f47ce2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifDouble : ExifValue + { + public ExifDouble(ExifTag tag) + : base(tag) + { + } + + public ExifDouble(ExifTagValue tag) + : base(tag) + { + } + + private ExifDouble(ExifDouble value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.DoubleFloat; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + this.Value = intValue; + return true; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifDouble(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs new file mode 100644 index 0000000000..259d5c98a2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifDoubleArray : ExifArrayValue + { + public ExifDoubleArray(ExifTag tag) + : base(tag) + { + } + + public ExifDoubleArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifDoubleArray(ExifDoubleArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.DoubleFloat; + + public override IExifValue DeepClone() => new ExifDoubleArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs new file mode 100644 index 0000000000..8d6c41f58e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifFloat : ExifValue + { + public ExifFloat(ExifTagValue tag) + : base(tag) + { + } + + private ExifFloat(ExifFloat value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SingleFloat; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + this.Value = intValue; + return true; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifFloat(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs new file mode 100644 index 0000000000..7789bc3b5d --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifFloatArray : ExifArrayValue + { + public ExifFloatArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifFloatArray(ExifFloatArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SingleFloat; + + public override IExifValue DeepClone() => new ExifFloatArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs new file mode 100644 index 0000000000..7f2f631a97 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifLong : ExifValue + { + public ExifLong(ExifTag tag) + : base(tag) + { + } + + public ExifLong(ExifTagValue tag) + : base(tag) + { + } + + private ExifLong(ExifLong value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Long; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + if (intValue >= uint.MinValue) + { + this.Value = (uint)intValue; + return true; + } + + return false; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifLong(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs new file mode 100644 index 0000000000..e05f50beee --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifLongArray : ExifArrayValue + { + public ExifLongArray(ExifTag tag) + : base(tag) + { + } + + public ExifLongArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifLongArray(ExifLongArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Long; + + public override IExifValue DeepClone() => new ExifLongArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs new file mode 100644 index 0000000000..ef7d20c855 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifNumber : ExifValue + { + public ExifNumber(ExifTag tag) + : base(tag) + { + } + + private ExifNumber(ExifNumber value) + : base(value) + { + } + + public override ExifDataType DataType + { + get + { + if (this.Value > ushort.MaxValue) + { + return ExifDataType.Long; + } + + return ExifDataType.Short; + } + } + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + if (intValue >= uint.MinValue) + { + this.Value = (uint)intValue; + return true; + } + + return false; + case uint uintValue: + this.Value = uintValue; + return true; + case short shortValue: + if (shortValue >= uint.MinValue) + { + this.Value = (uint)shortValue; + return true; + } + + return false; + case ushort ushortValue: + this.Value = ushortValue; + return true; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifNumber(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs new file mode 100644 index 0000000000..521cfc0857 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifNumberArray : ExifArrayValue + { + public ExifNumberArray(ExifTag tag) + : base(tag) + { + } + + private ExifNumberArray(ExifNumberArray value) + : base(value) + { + } + + public override ExifDataType DataType + { + get + { + if (this.Value is null) + { + return ExifDataType.Short; + } + + for (int i = 0; i < this.Value.Length; i++) + { + if (this.Value[i] > ushort.MaxValue) + { + return ExifDataType.Long; + } + } + + return ExifDataType.Short; + } + } + + public override IExifValue DeepClone() => new ExifNumberArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs new file mode 100644 index 0000000000..3ab77ab321 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifRational : ExifValue + { + public ExifRational(ExifTag tag) + : base(tag) + { + } + + public ExifRational(ExifTagValue tag) + : base(tag) + { + } + + private ExifRational(ExifRational value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Rational; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case SignedRational signed: + + if (signed.Numerator >= uint.MinValue && signed.Denominator >= uint.MinValue) + { + this.Value = new Rational((uint)signed.Numerator, (uint)signed.Denominator); + } + + return true; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifRational(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs new file mode 100644 index 0000000000..f78e363da4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifRationalArray : ExifArrayValue + { + public ExifRationalArray(ExifTag tag) + : base(tag) + { + } + + public ExifRationalArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifRationalArray(ExifRationalArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Rational; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + if (value is SignedRational[] signedArray) + { + return this.TrySetSignedArray(signedArray); + } + + if (value is SignedRational signed) + { + if (signed.Numerator >= 0 && signed.Denominator >= 0) + { + this.Value = new[] { new Rational((uint)signed.Numerator, (uint)signed.Denominator) }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifRationalArray(this); + + private bool TrySetSignedArray(SignedRational[] signed) + { + if (Array.FindIndex(signed, x => x.Numerator < 0 || x.Denominator < 0) > -1) + { + return false; + } + + var unsigned = new Rational[signed.Length]; + for (int i = 0; i < signed.Length; i++) + { + SignedRational s = signed[i]; + unsigned[i] = new Rational((uint)s.Numerator, (uint)s.Denominator); + } + + this.Value = unsigned; + return true; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs new file mode 100644 index 0000000000..b11f3fc9f9 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifShort : ExifValue + { + public ExifShort(ExifTag tag) + : base(tag) + { + } + + public ExifShort(ExifTagValue tag) + : base(tag) + { + } + + private ExifShort(ExifShort value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Short; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + if (intValue >= ushort.MinValue && intValue <= ushort.MaxValue) + { + this.Value = (ushort)intValue; + return true; + } + + return false; + case short shortValue: + if (shortValue >= ushort.MinValue) + { + this.Value = (ushort)shortValue; + return true; + } + + return false; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifShort(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs new file mode 100644 index 0000000000..379338c10f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifShortArray : ExifArrayValue + { + public ExifShortArray(ExifTag tag) + : base(tag) + { + } + + public ExifShortArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifShortArray(ExifShortArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Short; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + if (value is int[] signedIntArray) + { + return this.TrySetSignedIntArray(signedIntArray); + } + + if (value is short[] signedShortArray) + { + return this.TrySetSignedShortArray(signedShortArray); + } + + if (value is int signedInt) + { + if (signedInt >= ushort.MinValue && signedInt <= ushort.MaxValue) + { + this.Value = new ushort[] { (ushort)signedInt }; + } + + return true; + } + + if (value is short signedShort) + { + if (signedShort >= ushort.MinValue) + { + this.Value = new ushort[] { (ushort)signedShort }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifShortArray(this); + + private bool TrySetSignedIntArray(int[] signed) + { + if (Array.FindIndex(signed, x => x < ushort.MinValue || x > ushort.MaxValue) > -1) + { + return false; + } + + var unsigned = new ushort[signed.Length]; + for (int i = 0; i < signed.Length; i++) + { + int s = signed[i]; + unsigned[i] = (ushort)s; + } + + this.Value = unsigned; + return true; + } + + private bool TrySetSignedShortArray(short[] signed) + { + if (Array.FindIndex(signed, x => x < ushort.MinValue) > -1) + { + return false; + } + + var unsigned = new ushort[signed.Length]; + for (int i = 0; i < signed.Length; i++) + { + short s = signed[i]; + unsigned[i] = (ushort)s; + } + + this.Value = unsigned; + return true; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs new file mode 100644 index 0000000000..a9cb013ca5 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedByte : ExifValue + { + public ExifSignedByte(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedByte(ExifSignedByte value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedByte; + + protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + if (intValue >= sbyte.MinValue && intValue <= sbyte.MaxValue) + { + this.Value = (sbyte)intValue; + return true; + } + + return false; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifSignedByte(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs new file mode 100644 index 0000000000..b0d35cc8a8 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedByteArray : ExifArrayValue + { + public ExifSignedByteArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedByteArray(ExifSignedByteArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedByte; + + public override IExifValue DeepClone() => new ExifSignedByteArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs new file mode 100644 index 0000000000..c1e6808bf4 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedLong : ExifValue + { + public ExifSignedLong(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedLong(ExifSignedLong value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedLong; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override IExifValue DeepClone() => new ExifSignedLong(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs new file mode 100644 index 0000000000..36d4c00077 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedLongArray : ExifArrayValue + { + public ExifSignedLongArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedLongArray(ExifSignedLongArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedLong; + + public override IExifValue DeepClone() => new ExifSignedLongArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs new file mode 100644 index 0000000000..61fba979b3 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedRational : ExifValue + { + internal ExifSignedRational(ExifTag tag) + : base(tag) + { + } + + internal ExifSignedRational(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedRational(ExifSignedRational value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedRational; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override IExifValue DeepClone() => new ExifSignedRational(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs new file mode 100644 index 0000000000..2545bd9b27 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedRationalArray : ExifArrayValue + { + public ExifSignedRationalArray(ExifTag tag) + : base(tag) + { + } + + public ExifSignedRationalArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedRationalArray(ExifSignedRationalArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedRational; + + public override IExifValue DeepClone() => new ExifSignedRationalArray(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs new file mode 100644 index 0000000000..e00f5c085b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedShort : ExifValue + { + public ExifSignedShort(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedShort(ExifSignedShort value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedShort; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + if (intValue >= short.MinValue && intValue <= short.MaxValue) + { + this.Value = (short)intValue; + return true; + } + + return false; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifSignedShort(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs new file mode 100644 index 0000000000..403a501863 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedShortArray : ExifArrayValue + { + public ExifSignedShortArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedShortArray(ExifSignedShortArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedShort; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + if (value is int[] intArray) + { + return this.TrySetSignedArray(intArray); + } + + if (value is int intValue) + { + if (intValue >= short.MinValue && intValue <= short.MaxValue) + { + this.Value = new short[] { (short)intValue }; + } + + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifSignedShortArray(this); + + private bool TrySetSignedArray(int[] intArray) + { + if (Array.FindIndex(intArray, x => x < short.MinValue || x > short.MaxValue) > -1) + { + return false; + } + + var value = new short[intArray.Length]; + for (int i = 0; i < intArray.Length; i++) + { + int s = intArray[i]; + value[i] = (short)s; + } + + this.Value = value; + return true; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs new file mode 100644 index 0000000000..0678bc3e41 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifString : ExifValue + { + public ExifString(ExifTag tag) + : base(tag) + { + } + + public ExifString(ExifTagValue tag) + : base(tag) + { + } + + private ExifString(ExifString value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Ascii; + + protected override string StringValue => this.Value; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int intValue: + this.Value = intValue.ToString(CultureInfo.InvariantCulture); + return true; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifString(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs new file mode 100644 index 0000000000..547e099c92 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal abstract class ExifValue : IExifValue, IEquatable + { + protected ExifValue(ExifTag tag) => this.Tag = tag; + + protected ExifValue(ExifTagValue tag) => this.Tag = new UnkownExifTag(tag); + + internal ExifValue(ExifValue other) + { + Guard.NotNull(other, nameof(other)); + + this.DataType = other.DataType; + this.IsArray = other.IsArray; + this.Tag = other.Tag; + + if (!other.IsArray) + { + // All types are value types except for string which is immutable so safe to simply assign. + this.TrySetValue(other.GetValue()); + } + else + { + // All array types are value types so Clone() is sufficient here. + var array = (Array)other.GetValue(); + this.TrySetValue(array.Clone()); + } + } + + public virtual ExifDataType DataType { get; } + + public virtual bool IsArray { get; } + + public ExifTag Tag { get; } + + public static bool operator ==(ExifValue left, ExifTag right) => Equals(left, right); + + public static bool operator !=(ExifValue left, ExifTag right) => !Equals(left, right); + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is ExifTag tag) + { + return this.Equals(tag); + } + + if (obj is ExifValue value) + { + return this.Tag.Equals(value.Tag) && Equals(this.GetValue(), value.GetValue()); + } + + return false; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(ExifTag other) => this.Tag.Equals(other); + + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.Tag, this.GetValue()); + + public abstract object GetValue(); + + public abstract bool TrySetValue(object value); + + public abstract IExifValue DeepClone(); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs new file mode 100644 index 0000000000..62d3f40ac2 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -0,0 +1,307 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static partial class ExifValues + { + public static ExifValue Create(ExifTagValue tag) => (ExifValue)CreateValue(tag); + + public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag); + + public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents) + { + bool isArray = numberOfComponents != 1; + + switch (dataType) + { + case ExifDataType.Byte: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + case ExifDataType.DoubleFloat: return isArray ? (ExifValue)new ExifDoubleArray(tag) : new ExifDouble(tag); + case ExifDataType.SingleFloat: return isArray ? (ExifValue)new ExifFloatArray(tag) : new ExifFloat(tag); + case ExifDataType.Long: return isArray ? (ExifValue)new ExifLongArray(tag) : new ExifLong(tag); + case ExifDataType.Rational: return isArray ? (ExifValue)new ExifRationalArray(tag) : new ExifRational(tag); + case ExifDataType.Short: return isArray ? (ExifValue)new ExifShortArray(tag) : new ExifShort(tag); + case ExifDataType.SignedByte: return isArray ? (ExifValue)new ExifSignedByteArray(tag) : new ExifSignedByte(tag); + case ExifDataType.SignedLong: return isArray ? (ExifValue)new ExifSignedLongArray(tag) : new ExifSignedLong(tag); + case ExifDataType.SignedRational: return isArray ? (ExifValue)new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); + case ExifDataType.SignedShort: return isArray ? (ExifValue)new ExifSignedShortArray(tag) : new ExifSignedShort(tag); + case ExifDataType.Ascii: return new ExifString(tag); + case ExifDataType.Undefined: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + default: return null; + } + } + + private static object CreateValue(ExifTagValue tag) + { + switch (tag) + { + case ExifTagValue.FaxProfile: return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); + case ExifTagValue.ModeNumber: return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); + case ExifTagValue.GPSAltitudeRef: return new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte); + + case ExifTagValue.ClipPath: return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte); + case ExifTagValue.VersionYear: return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); + case ExifTagValue.XMP: return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); + case ExifTagValue.CFAPattern2: return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); + case ExifTagValue.TIFFEPStandardID: return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); + case ExifTagValue.XPTitle: return new ExifByteArray(ExifTag.XPTitle, ExifDataType.Byte); + case ExifTagValue.XPComment: return new ExifByteArray(ExifTag.XPComment, ExifDataType.Byte); + case ExifTagValue.XPAuthor: return new ExifByteArray(ExifTag.XPAuthor, ExifDataType.Byte); + case ExifTagValue.XPKeywords: return new ExifByteArray(ExifTag.XPKeywords, ExifDataType.Byte); + case ExifTagValue.XPSubject: return new ExifByteArray(ExifTag.XPSubject, ExifDataType.Byte); + case ExifTagValue.GPSVersionID: return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); + + case ExifTagValue.PixelScale: return new ExifDoubleArray(ExifTag.PixelScale); + case ExifTagValue.IntergraphMatrix: return new ExifDoubleArray(ExifTag.IntergraphMatrix); + case ExifTagValue.ModelTiePoint: return new ExifDoubleArray(ExifTag.ModelTiePoint); + case ExifTagValue.ModelTransform: return new ExifDoubleArray(ExifTag.ModelTransform); + + case ExifTagValue.SubfileType: return new ExifLong(ExifTag.SubfileType); + case ExifTagValue.SubIFDOffset: return new ExifLong(ExifTag.SubIFDOffset); + case ExifTagValue.GPSIFDOffset: return new ExifLong(ExifTag.GPSIFDOffset); + case ExifTagValue.T4Options: return new ExifLong(ExifTag.T4Options); + case ExifTagValue.T6Options: return new ExifLong(ExifTag.T6Options); + case ExifTagValue.XClipPathUnits: return new ExifLong(ExifTag.XClipPathUnits); + case ExifTagValue.YClipPathUnits: return new ExifLong(ExifTag.YClipPathUnits); + case ExifTagValue.ProfileType: return new ExifLong(ExifTag.ProfileType); + case ExifTagValue.CodingMethods: return new ExifLong(ExifTag.CodingMethods); + case ExifTagValue.T82ptions: return new ExifLong(ExifTag.T82ptions); + case ExifTagValue.JPEGInterchangeFormat: return new ExifLong(ExifTag.JPEGInterchangeFormat); + case ExifTagValue.JPEGInterchangeFormatLength: return new ExifLong(ExifTag.JPEGInterchangeFormatLength); + case ExifTagValue.MDFileTag: return new ExifLong(ExifTag.MDFileTag); + case ExifTagValue.StandardOutputSensitivity: return new ExifLong(ExifTag.StandardOutputSensitivity); + case ExifTagValue.RecommendedExposureIndex: return new ExifLong(ExifTag.RecommendedExposureIndex); + case ExifTagValue.ISOSpeed: return new ExifLong(ExifTag.ISOSpeed); + case ExifTagValue.ISOSpeedLatitudeyyy: return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); + case ExifTagValue.ISOSpeedLatitudezzz: return new ExifLong(ExifTag.ISOSpeedLatitudezzz); + case ExifTagValue.FaxRecvParams: return new ExifLong(ExifTag.FaxRecvParams); + case ExifTagValue.FaxRecvTime: return new ExifLong(ExifTag.FaxRecvTime); + case ExifTagValue.ImageNumber: return new ExifLong(ExifTag.ImageNumber); + + case ExifTagValue.FreeOffsets: return new ExifLongArray(ExifTag.FreeOffsets); + case ExifTagValue.FreeByteCounts: return new ExifLongArray(ExifTag.FreeByteCounts); + case ExifTagValue.ColorResponseUnit: return new ExifLongArray(ExifTag.ColorResponseUnit); + case ExifTagValue.TileOffsets: return new ExifLongArray(ExifTag.TileOffsets); + case ExifTagValue.SMinSampleValue: return new ExifLongArray(ExifTag.SMinSampleValue); + case ExifTagValue.SMaxSampleValue: return new ExifLongArray(ExifTag.SMaxSampleValue); + case ExifTagValue.JPEGQTables: return new ExifLongArray(ExifTag.JPEGQTables); + case ExifTagValue.JPEGDCTables: return new ExifLongArray(ExifTag.JPEGDCTables); + case ExifTagValue.JPEGACTables: return new ExifLongArray(ExifTag.JPEGACTables); + case ExifTagValue.StripRowCounts: return new ExifLongArray(ExifTag.StripRowCounts); + case ExifTagValue.IntergraphRegisters: return new ExifLongArray(ExifTag.IntergraphRegisters); + case ExifTagValue.TimeZoneOffset: return new ExifLongArray(ExifTag.TimeZoneOffset); + + case ExifTagValue.ImageWidth: return new ExifNumber(ExifTag.ImageWidth); + case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); + case ExifTagValue.TileWidth: return new ExifNumber(ExifTag.TileWidth); + case ExifTagValue.TileLength: return new ExifNumber(ExifTag.TileLength); + case ExifTagValue.BadFaxLines: return new ExifNumber(ExifTag.BadFaxLines); + case ExifTagValue.ConsecutiveBadFaxLines: return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); + case ExifTagValue.PixelXDimension: return new ExifNumber(ExifTag.PixelXDimension); + case ExifTagValue.PixelYDimension: return new ExifNumber(ExifTag.PixelYDimension); + + case ExifTagValue.StripOffsets: return new ExifNumberArray(ExifTag.StripOffsets); + case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); + case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer); + + case ExifTagValue.XPosition: return new ExifRational(ExifTag.XPosition); + case ExifTagValue.YPosition: return new ExifRational(ExifTag.YPosition); + case ExifTagValue.XResolution: return new ExifRational(ExifTag.XResolution); + case ExifTagValue.YResolution: return new ExifRational(ExifTag.YResolution); + case ExifTagValue.BatteryLevel: return new ExifRational(ExifTag.BatteryLevel); + case ExifTagValue.ExposureTime: return new ExifRational(ExifTag.ExposureTime); + case ExifTagValue.FNumber: return new ExifRational(ExifTag.FNumber); + case ExifTagValue.MDScalePixel: return new ExifRational(ExifTag.MDScalePixel); + case ExifTagValue.CompressedBitsPerPixel: return new ExifRational(ExifTag.CompressedBitsPerPixel); + case ExifTagValue.ApertureValue: return new ExifRational(ExifTag.ApertureValue); + case ExifTagValue.MaxApertureValue: return new ExifRational(ExifTag.MaxApertureValue); + case ExifTagValue.SubjectDistance: return new ExifRational(ExifTag.SubjectDistance); + case ExifTagValue.FocalLength: return new ExifRational(ExifTag.FocalLength); + case ExifTagValue.FlashEnergy2: return new ExifRational(ExifTag.FlashEnergy2); + case ExifTagValue.FocalPlaneXResolution2: return new ExifRational(ExifTag.FocalPlaneXResolution2); + case ExifTagValue.FocalPlaneYResolution2: return new ExifRational(ExifTag.FocalPlaneYResolution2); + case ExifTagValue.ExposureIndex2: return new ExifRational(ExifTag.ExposureIndex2); + case ExifTagValue.Humidity: return new ExifRational(ExifTag.Humidity); + case ExifTagValue.Pressure: return new ExifRational(ExifTag.Pressure); + case ExifTagValue.Acceleration: return new ExifRational(ExifTag.Acceleration); + case ExifTagValue.FlashEnergy: return new ExifRational(ExifTag.FlashEnergy); + case ExifTagValue.FocalPlaneXResolution: return new ExifRational(ExifTag.FocalPlaneXResolution); + case ExifTagValue.FocalPlaneYResolution: return new ExifRational(ExifTag.FocalPlaneYResolution); + case ExifTagValue.ExposureIndex: return new ExifRational(ExifTag.ExposureIndex); + case ExifTagValue.DigitalZoomRatio: return new ExifRational(ExifTag.DigitalZoomRatio); + case ExifTagValue.GPSAltitude: return new ExifRational(ExifTag.GPSAltitude); + case ExifTagValue.GPSDOP: return new ExifRational(ExifTag.GPSDOP); + case ExifTagValue.GPSSpeed: return new ExifRational(ExifTag.GPSSpeed); + case ExifTagValue.GPSTrack: return new ExifRational(ExifTag.GPSTrack); + case ExifTagValue.GPSImgDirection: return new ExifRational(ExifTag.GPSImgDirection); + case ExifTagValue.GPSDestBearing: return new ExifRational(ExifTag.GPSDestBearing); + case ExifTagValue.GPSDestDistance: return new ExifRational(ExifTag.GPSDestDistance); + + case ExifTagValue.WhitePoint: return new ExifRationalArray(ExifTag.WhitePoint); + case ExifTagValue.PrimaryChromaticities: return new ExifRationalArray(ExifTag.PrimaryChromaticities); + case ExifTagValue.YCbCrCoefficients: return new ExifRationalArray(ExifTag.YCbCrCoefficients); + case ExifTagValue.ReferenceBlackWhite: return new ExifRationalArray(ExifTag.ReferenceBlackWhite); + case ExifTagValue.GPSLatitude: return new ExifRationalArray(ExifTag.GPSLatitude); + case ExifTagValue.GPSLongitude: return new ExifRationalArray(ExifTag.GPSLongitude); + case ExifTagValue.GPSTimestamp: return new ExifRationalArray(ExifTag.GPSTimestamp); + case ExifTagValue.GPSDestLatitude: return new ExifRationalArray(ExifTag.GPSDestLatitude); + case ExifTagValue.GPSDestLongitude: return new ExifRationalArray(ExifTag.GPSDestLongitude); + case ExifTagValue.LensSpecification: return new ExifRationalArray(ExifTag.LensSpecification); + + case ExifTagValue.OldSubfileType: return new ExifShort(ExifTag.OldSubfileType); + case ExifTagValue.Compression: return new ExifShort(ExifTag.Compression); + case ExifTagValue.PhotometricInterpretation: return new ExifShort(ExifTag.PhotometricInterpretation); + case ExifTagValue.Thresholding: return new ExifShort(ExifTag.Thresholding); + case ExifTagValue.CellWidth: return new ExifShort(ExifTag.CellWidth); + case ExifTagValue.CellLength: return new ExifShort(ExifTag.CellLength); + case ExifTagValue.FillOrder: return new ExifShort(ExifTag.FillOrder); + case ExifTagValue.Orientation: return new ExifShort(ExifTag.Orientation); + case ExifTagValue.SamplesPerPixel: return new ExifShort(ExifTag.SamplesPerPixel); + case ExifTagValue.PlanarConfiguration: return new ExifShort(ExifTag.PlanarConfiguration); + case ExifTagValue.GrayResponseUnit: return new ExifShort(ExifTag.GrayResponseUnit); + case ExifTagValue.ResolutionUnit: return new ExifShort(ExifTag.ResolutionUnit); + case ExifTagValue.CleanFaxData: return new ExifShort(ExifTag.CleanFaxData); + case ExifTagValue.InkSet: return new ExifShort(ExifTag.InkSet); + case ExifTagValue.NumberOfInks: return new ExifShort(ExifTag.NumberOfInks); + case ExifTagValue.DotRange: return new ExifShort(ExifTag.DotRange); + case ExifTagValue.Indexed: return new ExifShort(ExifTag.Indexed); + case ExifTagValue.OPIProxy: return new ExifShort(ExifTag.OPIProxy); + case ExifTagValue.JPEGProc: return new ExifShort(ExifTag.JPEGProc); + case ExifTagValue.JPEGRestartInterval: return new ExifShort(ExifTag.JPEGRestartInterval); + case ExifTagValue.YCbCrPositioning: return new ExifShort(ExifTag.YCbCrPositioning); + case ExifTagValue.Rating: return new ExifShort(ExifTag.Rating); + case ExifTagValue.RatingPercent: return new ExifShort(ExifTag.RatingPercent); + case ExifTagValue.ExposureProgram: return new ExifShort(ExifTag.ExposureProgram); + case ExifTagValue.Interlace: return new ExifShort(ExifTag.Interlace); + case ExifTagValue.SelfTimerMode: return new ExifShort(ExifTag.SelfTimerMode); + case ExifTagValue.SensitivityType: return new ExifShort(ExifTag.SensitivityType); + case ExifTagValue.MeteringMode: return new ExifShort(ExifTag.MeteringMode); + case ExifTagValue.LightSource: return new ExifShort(ExifTag.LightSource); + case ExifTagValue.FocalPlaneResolutionUnit2: return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); + case ExifTagValue.SensingMethod2: return new ExifShort(ExifTag.SensingMethod2); + case ExifTagValue.Flash: return new ExifShort(ExifTag.Flash); + case ExifTagValue.ColorSpace: return new ExifShort(ExifTag.ColorSpace); + case ExifTagValue.FocalPlaneResolutionUnit: return new ExifShort(ExifTag.FocalPlaneResolutionUnit); + case ExifTagValue.SensingMethod: return new ExifShort(ExifTag.SensingMethod); + case ExifTagValue.CustomRendered: return new ExifShort(ExifTag.CustomRendered); + case ExifTagValue.ExposureMode: return new ExifShort(ExifTag.ExposureMode); + case ExifTagValue.WhiteBalance: return new ExifShort(ExifTag.WhiteBalance); + case ExifTagValue.FocalLengthIn35mmFilm: return new ExifShort(ExifTag.FocalLengthIn35mmFilm); + case ExifTagValue.SceneCaptureType: return new ExifShort(ExifTag.SceneCaptureType); + case ExifTagValue.GainControl: return new ExifShort(ExifTag.GainControl); + case ExifTagValue.Contrast: return new ExifShort(ExifTag.Contrast); + case ExifTagValue.Saturation: return new ExifShort(ExifTag.Saturation); + case ExifTagValue.Sharpness: return new ExifShort(ExifTag.Sharpness); + case ExifTagValue.SubjectDistanceRange: return new ExifShort(ExifTag.SubjectDistanceRange); + case ExifTagValue.GPSDifferential: return new ExifShort(ExifTag.GPSDifferential); + + case ExifTagValue.BitsPerSample: return new ExifShortArray(ExifTag.BitsPerSample); + case ExifTagValue.MinSampleValue: return new ExifShortArray(ExifTag.MinSampleValue); + case ExifTagValue.MaxSampleValue: return new ExifShortArray(ExifTag.MaxSampleValue); + case ExifTagValue.GrayResponseCurve: return new ExifShortArray(ExifTag.GrayResponseCurve); + case ExifTagValue.ColorMap: return new ExifShortArray(ExifTag.ColorMap); + case ExifTagValue.ExtraSamples: return new ExifShortArray(ExifTag.ExtraSamples); + case ExifTagValue.PageNumber: return new ExifShortArray(ExifTag.PageNumber); + case ExifTagValue.TransferFunction: return new ExifShortArray(ExifTag.TransferFunction); + case ExifTagValue.Predictor: return new ExifShortArray(ExifTag.Predictor); + case ExifTagValue.HalftoneHints: return new ExifShortArray(ExifTag.HalftoneHints); + case ExifTagValue.SampleFormat: return new ExifShortArray(ExifTag.SampleFormat); + case ExifTagValue.TransferRange: return new ExifShortArray(ExifTag.TransferRange); + case ExifTagValue.DefaultImageColor: return new ExifShortArray(ExifTag.DefaultImageColor); + case ExifTagValue.JPEGLosslessPredictors: return new ExifShortArray(ExifTag.JPEGLosslessPredictors); + case ExifTagValue.JPEGPointTransforms: return new ExifShortArray(ExifTag.JPEGPointTransforms); + case ExifTagValue.YCbCrSubsampling: return new ExifShortArray(ExifTag.YCbCrSubsampling); + case ExifTagValue.CFARepeatPatternDim: return new ExifShortArray(ExifTag.CFARepeatPatternDim); + case ExifTagValue.IntergraphPacketData: return new ExifShortArray(ExifTag.IntergraphPacketData); + case ExifTagValue.ISOSpeedRatings: return new ExifShortArray(ExifTag.ISOSpeedRatings); + case ExifTagValue.SubjectArea: return new ExifShortArray(ExifTag.SubjectArea); + case ExifTagValue.SubjectLocation: return new ExifShortArray(ExifTag.SubjectLocation); + + case ExifTagValue.ShutterSpeedValue: return new ExifSignedRational(ExifTag.ShutterSpeedValue); + case ExifTagValue.BrightnessValue: return new ExifSignedRational(ExifTag.BrightnessValue); + case ExifTagValue.ExposureBiasValue: return new ExifSignedRational(ExifTag.ExposureBiasValue); + case ExifTagValue.AmbientTemperature: return new ExifSignedRational(ExifTag.AmbientTemperature); + case ExifTagValue.WaterDepth: return new ExifSignedRational(ExifTag.WaterDepth); + case ExifTagValue.CameraElevationAngle: return new ExifSignedRational(ExifTag.CameraElevationAngle); + + case ExifTagValue.Decode: return new ExifSignedRationalArray(ExifTag.Decode); + + case ExifTagValue.ImageDescription: return new ExifString(ExifTag.ImageDescription); + case ExifTagValue.Make: return new ExifString(ExifTag.Make); + case ExifTagValue.Model: return new ExifString(ExifTag.Model); + case ExifTagValue.Software: return new ExifString(ExifTag.Software); + case ExifTagValue.DateTime: return new ExifString(ExifTag.DateTime); + case ExifTagValue.Artist: return new ExifString(ExifTag.Artist); + case ExifTagValue.HostComputer: return new ExifString(ExifTag.HostComputer); + case ExifTagValue.Copyright: return new ExifString(ExifTag.Copyright); + case ExifTagValue.DocumentName: return new ExifString(ExifTag.DocumentName); + case ExifTagValue.PageName: return new ExifString(ExifTag.PageName); + case ExifTagValue.InkNames: return new ExifString(ExifTag.InkNames); + case ExifTagValue.TargetPrinter: return new ExifString(ExifTag.TargetPrinter); + case ExifTagValue.ImageID: return new ExifString(ExifTag.ImageID); + case ExifTagValue.MDLabName: return new ExifString(ExifTag.MDLabName); + case ExifTagValue.MDSampleInfo: return new ExifString(ExifTag.MDSampleInfo); + case ExifTagValue.MDPrepDate: return new ExifString(ExifTag.MDPrepDate); + case ExifTagValue.MDPrepTime: return new ExifString(ExifTag.MDPrepTime); + case ExifTagValue.MDFileUnits: return new ExifString(ExifTag.MDFileUnits); + case ExifTagValue.SEMInfo: return new ExifString(ExifTag.SEMInfo); + case ExifTagValue.SpectralSensitivity: return new ExifString(ExifTag.SpectralSensitivity); + case ExifTagValue.DateTimeOriginal: return new ExifString(ExifTag.DateTimeOriginal); + case ExifTagValue.DateTimeDigitized: return new ExifString(ExifTag.DateTimeDigitized); + case ExifTagValue.SubsecTime: return new ExifString(ExifTag.SubsecTime); + case ExifTagValue.SubsecTimeOriginal: return new ExifString(ExifTag.SubsecTimeOriginal); + case ExifTagValue.SubsecTimeDigitized: return new ExifString(ExifTag.SubsecTimeDigitized); + case ExifTagValue.RelatedSoundFile: return new ExifString(ExifTag.RelatedSoundFile); + case ExifTagValue.FaxSubaddress: return new ExifString(ExifTag.FaxSubaddress); + case ExifTagValue.OffsetTime: return new ExifString(ExifTag.OffsetTime); + case ExifTagValue.OffsetTimeOriginal: return new ExifString(ExifTag.OffsetTimeOriginal); + case ExifTagValue.OffsetTimeDigitized: return new ExifString(ExifTag.OffsetTimeDigitized); + case ExifTagValue.SecurityClassification: return new ExifString(ExifTag.SecurityClassification); + case ExifTagValue.ImageHistory: return new ExifString(ExifTag.ImageHistory); + case ExifTagValue.ImageUniqueID: return new ExifString(ExifTag.ImageUniqueID); + case ExifTagValue.OwnerName: return new ExifString(ExifTag.OwnerName); + case ExifTagValue.SerialNumber: return new ExifString(ExifTag.SerialNumber); + case ExifTagValue.LensMake: return new ExifString(ExifTag.LensMake); + case ExifTagValue.LensModel: return new ExifString(ExifTag.LensModel); + case ExifTagValue.LensSerialNumber: return new ExifString(ExifTag.LensSerialNumber); + case ExifTagValue.GDALMetadata: return new ExifString(ExifTag.GDALMetadata); + case ExifTagValue.GDALNoData: return new ExifString(ExifTag.GDALNoData); + case ExifTagValue.GPSLatitudeRef: return new ExifString(ExifTag.GPSLatitudeRef); + case ExifTagValue.GPSLongitudeRef: return new ExifString(ExifTag.GPSLongitudeRef); + case ExifTagValue.GPSSatellites: return new ExifString(ExifTag.GPSSatellites); + case ExifTagValue.GPSStatus: return new ExifString(ExifTag.GPSStatus); + case ExifTagValue.GPSMeasureMode: return new ExifString(ExifTag.GPSMeasureMode); + case ExifTagValue.GPSSpeedRef: return new ExifString(ExifTag.GPSSpeedRef); + case ExifTagValue.GPSTrackRef: return new ExifString(ExifTag.GPSTrackRef); + case ExifTagValue.GPSImgDirectionRef: return new ExifString(ExifTag.GPSImgDirectionRef); + case ExifTagValue.GPSMapDatum: return new ExifString(ExifTag.GPSMapDatum); + case ExifTagValue.GPSDestLatitudeRef: return new ExifString(ExifTag.GPSDestLatitudeRef); + case ExifTagValue.GPSDestLongitudeRef: return new ExifString(ExifTag.GPSDestLongitudeRef); + case ExifTagValue.GPSDestBearingRef: return new ExifString(ExifTag.GPSDestBearingRef); + case ExifTagValue.GPSDestDistanceRef: return new ExifString(ExifTag.GPSDestDistanceRef); + case ExifTagValue.GPSDateStamp: return new ExifString(ExifTag.GPSDateStamp); + + case ExifTagValue.FileSource: return new ExifByte(ExifTag.FileSource, ExifDataType.Undefined); + case ExifTagValue.SceneType: return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); + + case ExifTagValue.JPEGTables: return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined); + case ExifTagValue.OECF: return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); + case ExifTagValue.ExifVersion: return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); + case ExifTagValue.ComponentsConfiguration: return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); + case ExifTagValue.MakerNote: return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); + case ExifTagValue.UserComment: return new ExifByteArray(ExifTag.UserComment, ExifDataType.Undefined); + case ExifTagValue.FlashpixVersion: return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse: return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse2: return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); + case ExifTagValue.Noise: return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); + case ExifTagValue.CFAPattern: return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); + case ExifTagValue.DeviceSettingDescription: return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); + case ExifTagValue.ImageSourceData: return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); + case ExifTagValue.GPSProcessingMethod: return new ExifByteArray(ExifTag.GPSProcessingMethod, ExifDataType.Undefined); + case ExifTagValue.GPSAreaInformation: return new ExifByteArray(ExifTag.GPSAreaInformation, ExifDataType.Undefined); + + default: return null; + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs new file mode 100644 index 0000000000..601630af6b --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal abstract class ExifValue : ExifValue, IExifValue + { + protected ExifValue(ExifTag tag) + : base(tag) + { + } + + protected ExifValue(ExifTagValue tag) + : base(tag) + { + } + + internal ExifValue(ExifValue value) + : base(value) + { + } + + public TValueType Value { get; set; } + + /// + /// Gets the value of the current instance as a string. + /// + protected abstract string StringValue { get; } + + public override object GetValue() => this.Value; + + public override bool TrySetValue(object value) + { + if (value is null) + { + this.Value = default; + return true; + } + + // We use type comparison here over "is" to avoid compiler optimizations + // that equate short with ushort, and sbyte with byte. + if (value.GetType() == typeof(TValueType)) + { + this.Value = (TValueType)value; + return true; + } + + return false; + } + + public override string ToString() + { + if (this.Value == null) + { + return null; + } + + string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, this.Value); + return description ?? this.StringValue; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs new file mode 100644 index 0000000000..50c4218320 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// A value of the exif profile. + /// + public interface IExifValue : IDeepCloneable + { + /// + /// Gets the data type of the exif value. + /// + ExifDataType DataType { get; } + + /// + /// Gets a value indicating whether the value is an array. + /// + bool IsArray { get; } + + /// + /// Gets the tag of the exif value. + /// + ExifTag Tag { get; } + + /// + /// Gets the value of this exif value. + /// + /// The value of this exif value. + object GetValue(); + + /// + /// Sets the value of this exif value. + /// + /// The value of this exif value. + /// A value indicating whether the value could be set. + bool TrySetValue(object value); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs new file mode 100644 index 0000000000..72b93ddf9e --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// A value of the exif profile. + /// + /// The type of the value. + public interface IExifValue : IExifValue + { + /// + /// Gets or sets the value. + /// + TValueType Value { get; set; } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs new file mode 100644 index 0000000000..a30e45ddeb --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -0,0 +1,902 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a tag data entry + /// + /// The table entry with reading information + /// the tag data entry + public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) + { + this.currentIndex = (int)info.Offset; + IccTypeSignature type = this.ReadTagDataEntryHeader(); + + switch (type) + { + case IccTypeSignature.Chromaticity: + return this.ReadChromaticityTagDataEntry(); + case IccTypeSignature.ColorantOrder: + return this.ReadColorantOrderTagDataEntry(); + case IccTypeSignature.ColorantTable: + return this.ReadColorantTableTagDataEntry(); + case IccTypeSignature.Curve: + return this.ReadCurveTagDataEntry(); + case IccTypeSignature.Data: + return this.ReadDataTagDataEntry(info.DataSize); + case IccTypeSignature.DateTime: + return this.ReadDateTimeTagDataEntry(); + case IccTypeSignature.Lut16: + return this.ReadLut16TagDataEntry(); + case IccTypeSignature.Lut8: + return this.ReadLut8TagDataEntry(); + case IccTypeSignature.LutAToB: + return this.ReadLutAtoBTagDataEntry(); + case IccTypeSignature.LutBToA: + return this.ReadLutBtoATagDataEntry(); + case IccTypeSignature.Measurement: + return this.ReadMeasurementTagDataEntry(); + case IccTypeSignature.MultiLocalizedUnicode: + return this.ReadMultiLocalizedUnicodeTagDataEntry(); + case IccTypeSignature.MultiProcessElements: + return this.ReadMultiProcessElementsTagDataEntry(); + case IccTypeSignature.NamedColor2: + return this.ReadNamedColor2TagDataEntry(); + case IccTypeSignature.ParametricCurve: + return this.ReadParametricCurveTagDataEntry(); + case IccTypeSignature.ProfileSequenceDesc: + return this.ReadProfileSequenceDescTagDataEntry(); + case IccTypeSignature.ProfileSequenceIdentifier: + return this.ReadProfileSequenceIdentifierTagDataEntry(); + case IccTypeSignature.ResponseCurveSet16: + return this.ReadResponseCurveSet16TagDataEntry(); + case IccTypeSignature.S15Fixed16Array: + return this.ReadFix16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.Signature: + return this.ReadSignatureTagDataEntry(); + case IccTypeSignature.Text: + return this.ReadTextTagDataEntry(info.DataSize); + case IccTypeSignature.U16Fixed16Array: + return this.ReadUFix16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt16Array: + return this.ReadUInt16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt32Array: + return this.ReadUInt32ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt64Array: + return this.ReadUInt64ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt8Array: + return this.ReadUInt8ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.ViewingConditions: + return this.ReadViewingConditionsTagDataEntry(); + case IccTypeSignature.Xyz: + return this.ReadXyzTagDataEntry(info.DataSize); + + // V2 Types: + case IccTypeSignature.TextDescription: + return this.ReadTextDescriptionTagDataEntry(); + case IccTypeSignature.CrdInfo: + return this.ReadCrdInfoTagDataEntry(); + case IccTypeSignature.Screening: + return this.ReadScreeningTagDataEntry(); + case IccTypeSignature.UcrBg: + return this.ReadUcrBgTagDataEntry(info.DataSize); + + // Unsupported or unknown + case IccTypeSignature.DeviceSettings: + case IccTypeSignature.NamedColor: + case IccTypeSignature.Unknown: + default: + return this.ReadUnknownTagDataEntry(info.DataSize); + } + } + + /// + /// Reads the header of a + /// + /// The read signature + public IccTypeSignature ReadTagDataEntryHeader() + { + var type = (IccTypeSignature)this.ReadUInt32(); + this.AddIndex(4); // 4 bytes are not used + return type; + } + + /// + /// Reads the header of a and checks if it's the expected value + /// + /// expected value to check against + public void ReadCheckTagDataEntryHeader(IccTypeSignature expected) + { + IccTypeSignature type = this.ReadTagDataEntryHeader(); + if (expected != (IccTypeSignature)uint.MaxValue && type != expected) + { + throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}"); + } + } + + /// + /// Reads a with an unknown + /// + /// The size of the entry in bytes + /// The read entry + public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + return new IccUnknownTagDataEntry(this.ReadBytes(count)); + } + + /// + /// Reads a + /// + /// The read entry + public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry() + { + ushort channelCount = this.ReadUInt16(); + var colorant = (IccColorantEncoding)this.ReadUInt16(); + + if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown) + { + // The type is known and so are the values (they are constant) + // channelCount should always be 3 but it doesn't really matter if it's not + return new IccChromaticityTagDataEntry(colorant); + } + else + { + // The type is not know, so the values need be read + double[][] values = new double[channelCount][]; + for (int i = 0; i < channelCount; i++) + { + values[i] = new double[] { this.ReadUFix16(), this.ReadUFix16() }; + } + + return new IccChromaticityTagDataEntry(values); + } + } + + /// + /// Reads a + /// + /// The read entry + public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + byte[] number = this.ReadBytes((int)colorantCount); + return new IccColorantOrderTagDataEntry(number); + } + + /// + /// Reads a + /// + /// The read entry + public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + var cdata = new IccColorantTableEntry[colorantCount]; + for (int i = 0; i < colorantCount; i++) + { + cdata[i] = this.ReadColorantTableEntry(); + } + + return new IccColorantTableTagDataEntry(cdata); + } + + /// + /// Reads a + /// + /// The read entry + public IccCurveTagDataEntry ReadCurveTagDataEntry() + { + uint pointCount = this.ReadUInt32(); + + if (pointCount == 0) + { + return new IccCurveTagDataEntry(); + } + + if (pointCount == 1) + { + return new IccCurveTagDataEntry(this.ReadUFix8()); + } + + float[] cdata = new float[pointCount]; + for (int i = 0; i < pointCount; i++) + { + cdata[i] = this.ReadUInt16() / 65535f; + } + + return new IccCurveTagDataEntry(cdata); + + // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccDataTagDataEntry ReadDataTagDataEntry(uint size) + { + this.AddIndex(3); // first 3 bytes are zero + byte b = this.data[this.AddIndex(1)]; + + // last bit of 4th byte is either 0 = ASCII or 1 = binary + bool ascii = this.GetBit(b, 7); + int length = (int)size - 12; + byte[] cdata = this.ReadBytes(length); + + return new IccDataTagDataEntry(cdata, ascii); + } + + /// + /// Reads a + /// + /// The read entry + public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() + { + return new IccDateTimeTagDataEntry(this.ReadDateTime()); + } + + /// + /// Reads a + /// + /// The read entry + public IccLut16TagDataEntry ReadLut16TagDataEntry() + { + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + byte clutPointCount = this.data[this.AddIndex(1)]; + this.AddIndex(1); // 1 byte reserved + + float[,] matrix = this.ReadMatrix(3, 3, false); + + ushort inTableCount = this.ReadUInt16(); + ushort outTableCount = this.ReadUInt16(); + + // Input LUT + var inValues = new IccLut[inChCount]; + byte[] gridPointCount = new byte[inChCount]; + for (int i = 0; i < inChCount; i++) + { + inValues[i] = this.ReadLut16(inTableCount); + gridPointCount[i] = clutPointCount; + } + + // CLUT + IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount); + + // Output LUT + var outValues = new IccLut[outChCount]; + for (int i = 0; i < outChCount; i++) + { + outValues[i] = this.ReadLut16(outTableCount); + } + + return new IccLut16TagDataEntry(matrix, inValues, clut, outValues); + } + + /// + /// Reads a + /// + /// The read entry + public IccLut8TagDataEntry ReadLut8TagDataEntry() + { + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + byte clutPointCount = this.data[this.AddIndex(1)]; + this.AddIndex(1); // 1 byte reserved + + float[,] matrix = this.ReadMatrix(3, 3, false); + + // Input LUT + var inValues = new IccLut[inChCount]; + byte[] gridPointCount = new byte[inChCount]; + for (int i = 0; i < inChCount; i++) + { + inValues[i] = this.ReadLut8(); + gridPointCount[i] = clutPointCount; + } + + // CLUT + IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount); + + // Output LUT + var outValues = new IccLut[outChCount]; + for (int i = 0; i < outChCount; i++) + { + outValues[i] = this.ReadLut8(); + } + + return new IccLut8TagDataEntry(matrix, inValues, clut, outValues); + } + + /// + /// Reads a + /// + /// The read entry + public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + this.AddIndex(2); // 2 bytes reserved + + uint bCurveOffset = this.ReadUInt32(); + uint matrixOffset = this.ReadUInt32(); + uint mCurveOffset = this.ReadUInt32(); + uint clutOffset = this.ReadUInt32(); + uint aCurveOffset = this.ReadUInt32(); + + IccTagDataEntry[] bCurve = null; + IccTagDataEntry[] mCurve = null; + IccTagDataEntry[] aCurve = null; + IccClut clut = null; + float[,] matrix3x3 = null; + float[] matrix3x1 = null; + + if (bCurveOffset != 0) + { + this.currentIndex = (int)bCurveOffset + start; + bCurve = this.ReadCurves(outChCount); + } + + if (mCurveOffset != 0) + { + this.currentIndex = (int)mCurveOffset + start; + mCurve = this.ReadCurves(outChCount); + } + + if (aCurveOffset != 0) + { + this.currentIndex = (int)aCurveOffset + start; + aCurve = this.ReadCurves(inChCount); + } + + if (clutOffset != 0) + { + this.currentIndex = (int)clutOffset + start; + clut = this.ReadClut(inChCount, outChCount, false); + } + + if (matrixOffset != 0) + { + this.currentIndex = (int)matrixOffset + start; + matrix3x3 = this.ReadMatrix(3, 3, false); + matrix3x1 = this.ReadMatrix(3, false); + } + + return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + } + + /// + /// Reads a + /// + /// The read entry + public IccLutBToATagDataEntry ReadLutBtoATagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + this.AddIndex(2); // 2 bytes reserved + + uint bCurveOffset = this.ReadUInt32(); + uint matrixOffset = this.ReadUInt32(); + uint mCurveOffset = this.ReadUInt32(); + uint clutOffset = this.ReadUInt32(); + uint aCurveOffset = this.ReadUInt32(); + + IccTagDataEntry[] bCurve = null; + IccTagDataEntry[] mCurve = null; + IccTagDataEntry[] aCurve = null; + IccClut clut = null; + float[,] matrix3x3 = null; + float[] matrix3x1 = null; + + if (bCurveOffset != 0) + { + this.currentIndex = (int)bCurveOffset + start; + bCurve = this.ReadCurves(inChCount); + } + + if (mCurveOffset != 0) + { + this.currentIndex = (int)mCurveOffset + start; + mCurve = this.ReadCurves(inChCount); + } + + if (aCurveOffset != 0) + { + this.currentIndex = (int)aCurveOffset + start; + aCurve = this.ReadCurves(outChCount); + } + + if (clutOffset != 0) + { + this.currentIndex = (int)clutOffset + start; + clut = this.ReadClut(inChCount, outChCount, false); + } + + if (matrixOffset != 0) + { + this.currentIndex = (int)matrixOffset + start; + matrix3x3 = this.ReadMatrix(3, 3, false); + matrix3x1 = this.ReadMatrix(3, false); + } + + return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + } + + /// + /// Reads a + /// + /// The read entry + public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() + { + return new IccMeasurementTagDataEntry( + observer: (IccStandardObserver)this.ReadUInt32(), + xyzBacking: this.ReadXyzNumber(), + geometry: (IccMeasurementGeometry)this.ReadUInt32(), + flare: this.ReadUFix16(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } + + /// + /// Reads a + /// + /// The read entry + public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + uint recordCount = this.ReadUInt32(); + + this.ReadUInt32(); // Record size (always 12) + var text = new IccLocalizedString[recordCount]; + + var culture = new CultureInfo[recordCount]; + uint[] length = new uint[recordCount]; + uint[] offset = new uint[recordCount]; + + for (int i = 0; i < recordCount; i++) + { + string languageCode = this.ReadAsciiString(2); + string countryCode = this.ReadAsciiString(2); + + culture[i] = ReadCulture(languageCode, countryCode); + length[i] = this.ReadUInt32(); + offset[i] = this.ReadUInt32(); + } + + for (int i = 0; i < recordCount; i++) + { + this.currentIndex = (int)(start + offset[i]); + text[i] = new IccLocalizedString(culture[i], this.ReadUnicodeString((int)length[i])); + } + + return new IccMultiLocalizedUnicodeTagDataEntry(text); + + CultureInfo ReadCulture(string language, string country) + { + if (string.IsNullOrWhiteSpace(language)) + { + return CultureInfo.InvariantCulture; + } + else if (string.IsNullOrWhiteSpace(country)) + { + try + { + return new CultureInfo(language); + } + catch (CultureNotFoundException) + { + return CultureInfo.InvariantCulture; + } + } + else + { + try + { + return new CultureInfo($"{language}-{country}"); + } + catch (CultureNotFoundException) + { + return ReadCulture(language, null); + } + } + } + } + + /// + /// Reads a + /// + /// The read entry + public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry() + { + int start = this.currentIndex - 8; + + this.ReadUInt16(); + this.ReadUInt16(); + uint elementCount = this.ReadUInt32(); + + var positionTable = new IccPositionNumber[elementCount]; + for (int i = 0; i < elementCount; i++) + { + positionTable[i] = this.ReadPositionNumber(); + } + + var elements = new IccMultiProcessElement[elementCount]; + for (int i = 0; i < elementCount; i++) + { + this.currentIndex = (int)positionTable[i].Offset + start; + elements[i] = this.ReadMultiProcessElement(); + } + + return new IccMultiProcessElementsTagDataEntry(elements); + } + + /// + /// Reads a + /// + /// The read entry + public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry() + { + int vendorFlag = this.ReadInt32(); + uint colorCount = this.ReadUInt32(); + uint coordCount = this.ReadUInt32(); + string prefix = this.ReadAsciiString(32); + string suffix = this.ReadAsciiString(32); + + var colors = new IccNamedColor[colorCount]; + for (int i = 0; i < colorCount; i++) + { + colors[i] = this.ReadNamedColor(coordCount); + } + + return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors); + } + + /// + /// Reads a + /// + /// The read entry + public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() + { + return new IccParametricCurveTagDataEntry(this.ReadParametricCurve()); + } + + /// + /// Reads a + /// + /// The read entry + public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry() + { + uint count = this.ReadUInt32(); + var description = new IccProfileDescription[count]; + for (int i = 0; i < count; i++) + { + description[i] = this.ReadProfileDescription(); + } + + return new IccProfileSequenceDescTagDataEntry(description); + } + + /// + /// Reads a + /// + /// The read entry + public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + uint count = this.ReadUInt32(); + var table = new IccPositionNumber[count]; + for (int i = 0; i < count; i++) + { + table[i] = this.ReadPositionNumber(); + } + + var entries = new IccProfileSequenceIdentifier[count]; + for (int i = 0; i < count; i++) + { + this.currentIndex = (int)(start + table[i].Offset); + IccProfileId id = this.ReadProfileId(); + this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode); + IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry(); + entries[i] = new IccProfileSequenceIdentifier(id, description.Texts); + } + + return new IccProfileSequenceIdentifierTagDataEntry(entries); + } + + /// + /// Reads a + /// + /// The read entry + public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + ushort channelCount = this.ReadUInt16(); + ushort measurementCount = this.ReadUInt16(); + + uint[] offset = new uint[measurementCount]; + for (int i = 0; i < measurementCount; i++) + { + offset[i] = this.ReadUInt32(); + } + + var curves = new IccResponseCurve[measurementCount]; + for (int i = 0; i < measurementCount; i++) + { + this.currentIndex = (int)(start + offset[i]); + curves[i] = this.ReadResponseCurve(channelCount); + } + + return new IccResponseCurveSet16TagDataEntry(curves); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + float[] arrayData = new float[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadFix16() / 256f; + } + + return new IccFix16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The read entry + public IccSignatureTagDataEntry ReadSignatureTagDataEntry() + { + return new IccSignatureTagDataEntry(this.ReadAsciiString(4)); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccTextTagDataEntry ReadTextTagDataEntry(uint size) + { + return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + float[] arrayData = new float[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUFix16(); + } + + return new IccUFix16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 2; + ushort[] arrayData = new ushort[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt16(); + } + + return new IccUInt16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + uint[] arrayData = new uint[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt32(); + } + + return new IccUInt32ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 8; + ulong[] arrayData = new ulong[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt64(); + } + + return new IccUInt64ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + byte[] adata = this.ReadBytes(count); + + return new IccUInt8ArrayTagDataEntry(adata); + } + + /// + /// Reads a + /// + /// The read entry + public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() + { + return new IccViewingConditionsTagDataEntry( + illuminantXyz: this.ReadXyzNumber(), + surroundXyz: this.ReadXyzNumber(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size) + { + uint count = (size - 8) / 12; + var arrayData = new Vector3[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadXyzNumber(); + } + + return new IccXyzTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The read entry + public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry() + { + string unicodeValue, scriptcodeValue; + string asciiValue = unicodeValue = scriptcodeValue = null; + + int asciiCount = (int)this.ReadUInt32(); + if (asciiCount > 0) + { + asciiValue = this.ReadAsciiString(asciiCount - 1); + this.AddIndex(1); // Null terminator + } + + uint unicodeLangCode = this.ReadUInt32(); + int unicodeCount = (int)this.ReadUInt32(); + if (unicodeCount > 0) + { + unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2); + this.AddIndex(2); // Null terminator + } + + ushort scriptcodeCode = this.ReadUInt16(); + int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67); + if (scriptcodeCount > 0) + { + scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1); + this.AddIndex(1); // Null terminator + } + + return new IccTextDescriptionTagDataEntry( + asciiValue, + unicodeValue, + scriptcodeValue, + unicodeLangCode, + scriptcodeCode); + } + + /// + /// Reads a + /// + /// The read entry + public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry() + { + uint productNameCount = this.ReadUInt32(); + string productName = this.ReadAsciiString((int)productNameCount); + + uint crd0Count = this.ReadUInt32(); + string crd0Name = this.ReadAsciiString((int)crd0Count); + + uint crd1Count = this.ReadUInt32(); + string crd1Name = this.ReadAsciiString((int)crd1Count); + + uint crd2Count = this.ReadUInt32(); + string crd2Name = this.ReadAsciiString((int)crd2Count); + + uint crd3Count = this.ReadUInt32(); + string crd3Name = this.ReadAsciiString((int)crd3Count); + + return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name); + } + + /// + /// Reads a + /// + /// The read entry + public IccScreeningTagDataEntry ReadScreeningTagDataEntry() + { + var flags = (IccScreeningFlag)this.ReadInt32(); + uint channelCount = this.ReadUInt32(); + var channels = new IccScreeningChannel[channelCount]; + for (int i = 0; i < channels.Length; i++) + { + channels[i] = this.ReadScreeningChannel(); + } + + return new IccScreeningTagDataEntry(flags, channels); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) + { + uint ucrCount = this.ReadUInt32(); + ushort[] ucrCurve = new ushort[ucrCount]; + for (int i = 0; i < ucrCurve.Length; i++) + { + ucrCurve[i] = this.ReadUInt16(); + } + + uint bgCount = this.ReadUInt32(); + ushort[] bgCurve = new ushort[bgCount]; + for (int i = 0; i < bgCurve.Length; i++) + { + bgCurve[i] = this.ReadUInt16(); + } + + // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount) + uint dataSize = ((ucrCount + bgCount) * 2) + 8; + int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size + string description = this.ReadAsciiString(descriptionLength); + + return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs new file mode 100644 index 0000000000..585892e96a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -0,0 +1,158 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Matrix4x4 value, bool isSingle) + { + int count = 0; + + if (isSingle) + { + count += this.WriteSingle(value.M11); + count += this.WriteSingle(value.M21); + count += this.WriteSingle(value.M31); + + count += this.WriteSingle(value.M12); + count += this.WriteSingle(value.M22); + count += this.WriteSingle(value.M32); + + count += this.WriteSingle(value.M13); + count += this.WriteSingle(value.M23); + count += this.WriteSingle(value.M33); + } + else + { + count += this.WriteFix16(value.M11); + count += this.WriteFix16(value.M21); + count += this.WriteFix16(value.M31); + + count += this.WriteFix16(value.M12); + count += this.WriteFix16(value.M22); + count += this.WriteFix16(value.M32); + + count += this.WriteFix16(value.M13); + count += this.WriteFix16(value.M23); + count += this.WriteFix16(value.M33); + } + + return count; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(in DenseMatrix value, bool isSingle) + { + int count = 0; + for (int y = 0; y < value.Rows; y++) + { + for (int x = 0; x < value.Columns; x++) + { + if (isSingle) + { + count += this.WriteSingle(value[x, y]); + } + else + { + count += this.WriteFix16(value[x, y]); + } + } + } + + return count; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(float[,] value, bool isSingle) + { + int count = 0; + for (int y = 0; y < value.GetLength(1); y++) + { + for (int x = 0; x < value.GetLength(0); x++) + { + if (isSingle) + { + count += this.WriteSingle(value[x, y]); + } + else + { + count += this.WriteFix16(value[x, y]); + } + } + } + + return count; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Vector3 value, bool isSingle) + { + int count = 0; + if (isSingle) + { + count += this.WriteSingle(value.X); + count += this.WriteSingle(value.Y); + count += this.WriteSingle(value.Z); + } + else + { + count += this.WriteFix16(value.X); + count += this.WriteFix16(value.Y); + count += this.WriteFix16(value.Z); + } + + return count; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(float[] value, bool isSingle) + { + int count = 0; + for (int i = 0; i < value.Length; i++) + { + if (isSingle) + { + count += this.WriteSingle(value[i]); + } + else + { + count += this.WriteFix16(value[i]); + } + } + + return count; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs rename to src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs rename to src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs rename to src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs rename to src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs rename to src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs new file mode 100644 index 0000000000..668883e1ae --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A matrix element to process data + /// + internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Two dimensional matrix with size of Input-Channels x Output-Channels + /// One dimensional matrix with size of Output-Channels x 1 + public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) + : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) + { + Guard.NotNull(matrixIxO, nameof(matrixIxO)); + Guard.NotNull(matrixOx1, nameof(matrixOx1)); + + bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; + Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); + + this.MatrixIxO = matrixIxO; + this.MatrixOx1 = matrixOx1; + } + + /// + /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels + /// + public DenseMatrix MatrixIxO { get; } + + /// + /// Gets the one dimensional matrix with size of Output-Channels x 1 + /// + public float[] MatrixOx1 { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccMatrixProcessElement element) + { + return this.EqualsMatrix(element) + && this.MatrixOx1.AsSpan().SequenceEqual(element.MatrixOx1); + } + + return false; + } + + /// + public bool Equals(IccMatrixProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + + private bool EqualsMatrix(IccMatrixProcessElement element) + { + return this.MatrixIxO.Equals(element.MatrixIxO); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs rename to src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs new file mode 100644 index 0000000000..0dfaef7d40 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -0,0 +1,300 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +// TODO: Review the use of base IccTagDataEntry comparison. +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform. + /// + internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + /// Tag Signature + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutAToB, tagSignature) + { + this.VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsAClutMMatrixB()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = 3; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsMMatrixB()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; + } + else if (this.IsAClutB()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = curveB.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) + { + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; + } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } + + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) => other is IccLutAToBTagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccLutAToBTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && EqualsCurve(this.CurveB, other.CurveB) + && EqualsCurve(this.CurveM, other.CurveM) + && EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) => obj is IccLutAToBTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + { + HashCode hashCode = default; + + hashCode.Add(this.Signature); + hashCode.Add(this.InputChannelCount); + hashCode.Add(this.OutputChannelCount); + hashCode.Add(this.Matrix3x3); + hashCode.Add(this.Matrix3x1); + hashCode.Add(this.ClutValues); + hashCode.Add(this.CurveB); + hashCode.Add(this.CurveM); + hashCode.Add(this.CurveA); + + return hashCode.ToHashCode(); + } + + private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves is null; + bool entryNull = entryCurves is null; + + if (thisNull && entryNull) + { + return true; + } + + if (entryNull) + { + return false; + } + + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsAClutMMatrixB() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsMMatrixB() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; + } + + private bool IsAClutB() + { + return this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsB() => this.CurveB != null; + + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) + { + bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); + } + } + + private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) + { + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); + } + + if (matrix3x3 != null) + { + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } + + private Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix is null) + { + return null; + } + + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix is null) + { + return null; + } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs new file mode 100644 index 0000000000..929a70ed86 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -0,0 +1,299 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +// TODO: Review the use of base IccTagDataEntry comparison. +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform. + /// + internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + /// Tag Signature + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutBToA, tagSignature) + { + this.VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsBMatrixMClutA()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + + this.InputChannelCount = 3; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsBMatrixM()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; + } + else if (this.IsBClutA()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + + this.InputChannelCount = curveB.Length; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) + { + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; + } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } + + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) => other is IccLutBToATagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccLutBToATagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && this.EqualsCurve(this.CurveB, other.CurveB) + && this.EqualsCurve(this.CurveM, other.CurveM) + && this.EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) => obj is IccLutBToATagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + { + HashCode hashCode = default; + hashCode.Add(this.Signature); + hashCode.Add(this.InputChannelCount); + hashCode.Add(this.OutputChannelCount); + hashCode.Add(this.Matrix3x3); + hashCode.Add(this.Matrix3x1); + hashCode.Add(this.ClutValues); + hashCode.Add(this.CurveB); + hashCode.Add(this.CurveM); + hashCode.Add(this.CurveA); + + return hashCode.ToHashCode(); + } + + private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves is null; + bool entryNull = entryCurves is null; + + if (thisNull && entryNull) + { + return true; + } + + if (entryNull) + { + return false; + } + + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsBMatrixMClutA() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsBMatrixM() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; + } + + private bool IsBClutA() + { + return this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsB() => this.CurveB != null; + + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) + { + bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); + } + } + + private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) + { + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); + } + + if (matrix3x3 != null) + { + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } + + private Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix is null) + { + return null; + } + + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix is null) + { + return null; + } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs new file mode 100644 index 0000000000..1e7d532310 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The XYZType contains an array of XYZ values. + /// + internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers. + public IccXyzTagDataEntry(Vector3[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers + /// Tag Signature + public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Xyz, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the XYZ numbers. + /// + public Vector3[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + if (base.Equals(other) && other is IccXyzTagDataEntry entry) + { + return this.Data.AsSpan().SequenceEqual(entry.Data); + } + + return false; + } + + /// + public bool Equals(IccXyzTagDataEntry other) + { + return this.Equals((IccTagDataEntry)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs similarity index 100% rename from src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs rename to src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf new file mode 100644 index 0000000000..b00355181c Binary files /dev/null and b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf differ diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs new file mode 100644 index 0000000000..f138cc650f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -0,0 +1,298 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +{ + /// + /// Represents an IPTC profile providing access to the collection of values. + /// + public sealed class IptcProfile : IDeepCloneable + { + private Collection values; + + /// + /// Initializes a new instance of the class. + /// + public IptcProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The byte array to read the iptc profile from. + public IptcProfile(byte[] data) + { + this.Data = data; + this.Initialize(); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another IPTC profile. + /// + /// The other IPTC profile, from which the clone should be made from. + private IptcProfile(IptcProfile other) + { + Guard.NotNull(other, nameof(other)); + + if (other.values != null) + { + this.values = new Collection(); + + foreach (IptcValue value in other.Values) + { + this.values.Add(value.DeepClone()); + } + } + + if (other.Data != null) + { + this.Data = new byte[other.Data.Length]; + other.Data.AsSpan().CopyTo(this.Data); + } + } + + /// + /// Gets the byte data of the IPTC profile. + /// + public byte[] Data { get; private set; } + + /// + /// Gets the values of this iptc profile. + /// + public IEnumerable Values + { + get + { + this.Initialize(); + return this.values; + } + } + + /// + public IptcProfile DeepClone() => new IptcProfile(this); + + /// + /// Returns all values with the specified tag. + /// + /// The tag of the iptc value. + /// The values found with the specified tag. + public List GetValues(IptcTag tag) + { + var iptcValues = new List(); + foreach (IptcValue iptcValue in this.Values) + { + if (iptcValue.Tag == tag) + { + iptcValues.Add(iptcValue); + } + } + + return iptcValues; + } + + /// + /// Removes all values with the specified tag. + /// + /// The tag of the iptc value to remove. + /// True when the value was found and removed. + public bool RemoveValue(IptcTag tag) + { + this.Initialize(); + + bool removed = false; + for (int i = this.values.Count - 1; i >= 0; i--) + { + if (this.values[i].Tag == tag) + { + this.values.RemoveAt(i); + removed = true; + } + } + + return removed; + } + + /// + /// Removes values with the specified tag and value. + /// + /// The tag of the iptc value to remove. + /// The value of the iptc item to remove. + /// True when the value was found and removed. + public bool RemoveValue(IptcTag tag, string value) + { + this.Initialize(); + + bool removed = false; + for (int i = this.values.Count - 1; i >= 0; i--) + { + if (this.values[i].Tag == tag && this.values[i].Value.Equals(value)) + { + this.values.RemoveAt(i); + removed = true; + } + } + + return removed; + } + + /// + /// Changes the encoding for all the values. + /// + /// The encoding to use when storing the bytes. + public void SetEncoding(Encoding encoding) + { + Guard.NotNull(encoding, nameof(encoding)); + + foreach (IptcValue value in this.Values) + { + value.Encoding = encoding; + } + } + + /// + /// Sets the value for the specified tag. + /// + /// The tag of the iptc value. + /// The encoding to use when storing the bytes. + /// The value. + /// + /// Indicates if length restrictions from the specification should be followed strictly. + /// Defaults to true. + /// + public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true) + { + Guard.NotNull(encoding, nameof(encoding)); + Guard.NotNull(value, nameof(value)); + + if (!tag.IsRepeatable()) + { + foreach (IptcValue iptcValue in this.Values) + { + if (iptcValue.Tag == tag) + { + iptcValue.Strict = strict; + iptcValue.Encoding = encoding; + iptcValue.Value = value; + return; + } + } + } + + this.values.Add(new IptcValue(tag, encoding, value, strict)); + } + + /// + /// Makes sure the datetime is formatted according to the iptc specification. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + /// The tag of the iptc value. + /// The datetime. + public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset) + { + if (!tag.IsDate() && !tag.IsTime()) + { + throw new ArgumentException("iptc tag is not a time or date type"); + } + + var formattedDate = tag.IsDate() + ? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture) + : dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture) + .Replace(":", string.Empty); + + this.SetValue(tag, Encoding.UTF8, formattedDate); + } + + /// + /// Sets the value of the specified tag. + /// + /// The tag of the iptc value. + /// The value. + /// + /// Indicates if length restrictions from the specification should be followed strictly. + /// Defaults to true. + /// + public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict); + + /// + /// Updates the data of the profile. + /// + public void UpdateData() + { + var length = 0; + foreach (IptcValue value in this.Values) + { + length += value.Length + 5; + } + + this.Data = new byte[length]; + + int i = 0; + foreach (IptcValue value in this.Values) + { + this.Data[i++] = 28; + this.Data[i++] = 2; + this.Data[i++] = (byte)value.Tag; + this.Data[i++] = (byte)(value.Length >> 8); + this.Data[i++] = (byte)value.Length; + if (value.Length > 0) + { + Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length); + i += value.Length; + } + } + } + + private void Initialize() + { + if (this.values != null) + { + return; + } + + this.values = new Collection(); + + if (this.Data == null || this.Data[0] != 0x1c) + { + return; + } + + int i = 0; + while (i + 4 < this.Data.Length) + { + if (this.Data[i++] != 28) + { + continue; + } + + i++; + + var tag = (IptcTag)this.Data[i++]; + + int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2)); + i += 2; + + var iptcData = new byte[count]; + if ((count > 0) && (i + count <= this.Data.Length)) + { + Buffer.BlockCopy(this.Data, i, iptcData, 0, count); + this.values.Add(new IptcValue(tag, iptcData, false)); + } + + i += count; + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs new file mode 100644 index 0000000000..7258a02917 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs @@ -0,0 +1,397 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +{ + /// + /// Provides enumeration of all IPTC tags relevant for images. + /// + public enum IptcTag + { + /// + /// Unknown. + /// + Unknown = -1, + + /// + /// Record version identifying the version of the Information Interchange Model. + /// Not repeatable. Max length is 2. + /// + RecordVersion = 0, + + /// + /// Object type, not repeatable. Max Length is 67. + /// + ObjectType = 3, + + /// + /// Object attribute. Max length is 68. + /// + ObjectAttribute = 4, + + /// + /// Object Name, not repeatable. Max length is 64. + /// + Name = 5, + + /// + /// Edit status, not repeatable. Max length is 64. + /// + EditStatus = 7, + + /// + /// Editorial update, not repeatable. Max length is 2. + /// + EditorialUpdate = 8, + + /// + /// Urgency, not repeatable. Max length is 2. + /// + Urgency = 10, + + /// + /// Subject Reference. Max length is 236. + /// + SubjectReference = 12, + + /// + /// Category, not repeatable. Max length is 3. + /// + Category = 15, + + /// + /// Supplemental categories. Max length is 32. + /// + SupplementalCategories = 20, + + /// + /// Fixture identifier, not repeatable. Max length is 32. + /// + FixtureIdentifier = 22, + + /// + /// Keywords. Max length is 64. + /// + Keywords = 25, + + /// + /// Location code. Max length is 3. + /// + LocationCode = 26, + + /// + /// Location name. Max length is 64. + /// + LocationName = 27, + + /// + /// Release date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + ReleaseDate = 30, + + /// + /// Release time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + ReleaseTime = 35, + + /// + /// Expiration date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + ExpirationDate = 37, + + /// + /// Expiration time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + ExpirationTime = 38, + + /// + /// Special instructions, not repeatable. Max length is 256. + /// + SpecialInstructions = 40, + + /// + /// Action advised, not repeatable. Max length is 2. + /// + ActionAdvised = 42, + + /// + /// Reference service. Max length is 10. + /// + ReferenceService = 45, + + /// + /// Reference date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + ReferenceDate = 47, + + /// + /// ReferenceNumber. Max length is 8. + /// + ReferenceNumber = 50, + + /// + /// Created date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + CreatedDate = 55, + + /// + /// Created time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + CreatedTime = 60, + + /// + /// Digital creation date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + DigitalCreationDate = 62, + + /// + /// Digital creation time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + DigitalCreationTime = 63, + + /// + /// Originating program, not repeatable. Max length is 32. + /// + OriginatingProgram = 65, + + /// + /// Program version, not repeatable. Max length is 10. + /// + ProgramVersion = 70, + + /// + /// Object cycle, not repeatable. Max length is 1. + /// + ObjectCycle = 75, + + /// + /// Byline. Max length is 32. + /// + Byline = 80, + + /// + /// Byline title. Max length is 32. + /// + BylineTitle = 85, + + /// + /// City, not repeatable. Max length is 32. + /// + City = 90, + + /// + /// Sub location, not repeatable. Max length is 32. + /// + SubLocation = 92, + + /// + /// Province/State, not repeatable. Max length is 32. + /// + ProvinceState = 95, + + /// + /// Country code, not repeatable. Max length is 3. + /// + CountryCode = 100, + + /// + /// Country, not repeatable. Max length is 64. + /// + Country = 101, + + /// + /// Original transmission reference, not repeatable. Max length is 32. + /// + OriginalTransmissionReference = 103, + + /// + /// Headline, not repeatable. Max length is 256. + /// + Headline = 105, + + /// + /// Credit, not repeatable. Max length is 32. + /// + Credit = 110, + + /// + /// Source, not repeatable. Max length is 32. + /// + Source = 115, + + /// + /// Copyright notice, not repeatable. Max length is 128. + /// + CopyrightNotice = 116, + + /// + /// Contact. Max length 128. + /// + Contact = 118, + + /// + /// Caption, not repeatable. Max length is 2000. + /// + Caption = 120, + + /// + /// Local caption. + /// + LocalCaption = 121, + + /// + /// Caption writer. Max length is 32. + /// + CaptionWriter = 122, + + /// + /// Image type, not repeatable. Max length is 2. + /// + ImageType = 130, + + /// + /// Image orientation, not repeatable. Max length is 1. + /// + ImageOrientation = 131, + + /// + /// Custom field 1 + /// + CustomField1 = 200, + + /// + /// Custom field 2 + /// + CustomField2 = 201, + + /// + /// Custom field 3 + /// + CustomField3 = 202, + + /// + /// Custom field 4 + /// + CustomField4 = 203, + + /// + /// Custom field 5 + /// + CustomField5 = 204, + + /// + /// Custom field 6 + /// + CustomField6 = 205, + + /// + /// Custom field 7 + /// + CustomField7 = 206, + + /// + /// Custom field 8 + /// + CustomField8 = 207, + + /// + /// Custom field 9 + /// + CustomField9 = 208, + + /// + /// Custom field 10 + /// + CustomField10 = 209, + + /// + /// Custom field 11 + /// + CustomField11 = 210, + + /// + /// Custom field 12 + /// + CustomField12 = 211, + + /// + /// Custom field 13 + /// + CustomField13 = 212, + + /// + /// Custom field 14 + /// + CustomField14 = 213, + + /// + /// Custom field 15 + /// + CustomField15 = 214, + + /// + /// Custom field 16 + /// + CustomField16 = 215, + + /// + /// Custom field 17 + /// + CustomField17 = 216, + + /// + /// Custom field 18 + /// + CustomField18 = 217, + + /// + /// Custom field 19 + /// + CustomField19 = 218, + + /// + /// Custom field 20 + /// + CustomField20 = 219, + } +} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs new file mode 100644 index 0000000000..6b39769a7f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +{ + /// + /// Extension methods for IPTC tags. + /// + public static class IptcTagExtensions + { + /// + /// Maximum length of the IPTC value with the given tag according to the specification. + /// + /// The tag to check the max length for. + /// The maximum length. + public static int MaxLength(this IptcTag tag) + { + return tag switch + { + IptcTag.RecordVersion => 2, + IptcTag.ObjectType => 67, + IptcTag.ObjectAttribute => 68, + IptcTag.Name => 64, + IptcTag.EditStatus => 64, + IptcTag.EditorialUpdate => 2, + IptcTag.Urgency => 1, + IptcTag.SubjectReference => 236, + IptcTag.Category => 3, + IptcTag.SupplementalCategories => 32, + IptcTag.FixtureIdentifier => 32, + IptcTag.Keywords => 64, + IptcTag.LocationCode => 3, + IptcTag.LocationName => 64, + IptcTag.ReleaseDate => 8, + IptcTag.ReleaseTime => 11, + IptcTag.ExpirationDate => 8, + IptcTag.ExpirationTime => 11, + IptcTag.SpecialInstructions => 256, + IptcTag.ActionAdvised => 2, + IptcTag.ReferenceService => 10, + IptcTag.ReferenceDate => 8, + IptcTag.ReferenceNumber => 8, + IptcTag.CreatedDate => 8, + IptcTag.CreatedTime => 11, + IptcTag.DigitalCreationDate => 8, + IptcTag.DigitalCreationTime => 11, + IptcTag.OriginatingProgram => 32, + IptcTag.ProgramVersion => 10, + IptcTag.ObjectCycle => 1, + IptcTag.Byline => 32, + IptcTag.BylineTitle => 32, + IptcTag.City => 32, + IptcTag.SubLocation => 32, + IptcTag.ProvinceState => 32, + IptcTag.CountryCode => 3, + IptcTag.Country => 64, + IptcTag.OriginalTransmissionReference => 32, + IptcTag.Headline => 256, + IptcTag.Credit => 32, + IptcTag.Source => 32, + IptcTag.CopyrightNotice => 128, + IptcTag.Contact => 128, + IptcTag.Caption => 2000, + IptcTag.CaptionWriter => 32, + IptcTag.ImageType => 2, + IptcTag.ImageOrientation => 1, + _ => 256 + }; + } + + /// + /// Determines if the given tag can be repeated according to the specification. + /// + /// The tag to check. + /// True, if the tag can occur multiple times. + public static bool IsRepeatable(this IptcTag tag) + { + switch (tag) + { + case IptcTag.RecordVersion: + case IptcTag.ObjectType: + case IptcTag.Name: + case IptcTag.EditStatus: + case IptcTag.EditorialUpdate: + case IptcTag.Urgency: + case IptcTag.Category: + case IptcTag.FixtureIdentifier: + case IptcTag.ReleaseDate: + case IptcTag.ReleaseTime: + case IptcTag.ExpirationDate: + case IptcTag.ExpirationTime: + case IptcTag.SpecialInstructions: + case IptcTag.ActionAdvised: + case IptcTag.CreatedDate: + case IptcTag.CreatedTime: + case IptcTag.DigitalCreationDate: + case IptcTag.DigitalCreationTime: + case IptcTag.OriginatingProgram: + case IptcTag.ProgramVersion: + case IptcTag.ObjectCycle: + case IptcTag.City: + case IptcTag.SubLocation: + case IptcTag.ProvinceState: + case IptcTag.CountryCode: + case IptcTag.Country: + case IptcTag.OriginalTransmissionReference: + case IptcTag.Headline: + case IptcTag.Credit: + case IptcTag.Source: + case IptcTag.CopyrightNotice: + case IptcTag.Caption: + case IptcTag.ImageType: + case IptcTag.ImageOrientation: + return false; + + default: + return true; + } + } + + /// + /// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD. + /// + /// The tag to check. + /// True, if its a datetime tag. + public static bool IsDate(this IptcTag tag) + { + switch (tag) + { + case IptcTag.CreatedDate: + case IptcTag.DigitalCreationDate: + case IptcTag.ExpirationDate: + case IptcTag.ReferenceDate: + case IptcTag.ReleaseDate: + return true; + + default: + return false; + } + } + + /// + /// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM. + /// + /// The tag to check. + /// True, if its a time tag. + public static bool IsTime(this IptcTag tag) + { + switch (tag) + { + case IptcTag.CreatedTime: + case IptcTag.DigitalCreationTime: + case IptcTag.ExpirationTime: + case IptcTag.ReleaseTime: + return true; + + default: + return false; + } + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs new file mode 100644 index 0000000000..e63781012a --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +{ + /// + /// Represents a single value of the IPTC profile. + /// + public sealed class IptcValue : IDeepCloneable + { + private byte[] data = Array.Empty(); + private Encoding encoding; + + internal IptcValue(IptcValue other) + { + if (other.data != null) + { + this.data = new byte[other.data.Length]; + other.data.AsSpan().CopyTo(this.data); + } + + if (other.Encoding != null) + { + this.Encoding = (Encoding)other.Encoding.Clone(); + } + + this.Tag = other.Tag; + this.Strict = other.Strict; + } + + internal IptcValue(IptcTag tag, byte[] value, bool strict) + { + Guard.NotNull(value, nameof(value)); + + this.Strict = strict; + this.Tag = tag; + this.data = value; + this.encoding = Encoding.UTF8; + } + + internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict) + { + this.Strict = strict; + this.Tag = tag; + this.encoding = encoding; + this.Value = value; + } + + internal IptcValue(IptcTag tag, string value, bool strict) + { + this.Strict = strict; + this.Tag = tag; + this.encoding = Encoding.UTF8; + this.Value = value; + } + + /// + /// Gets or sets the encoding to use for the Value. + /// + public Encoding Encoding + { + get => this.encoding; + set + { + if (value != null) + { + this.encoding = value; + } + } + } + + /// + /// Gets the tag of the iptc value. + /// + public IptcTag Tag { get; } + + /// + /// Gets or sets a value indicating whether to be enforce value length restrictions according + /// to the specification. + /// + public bool Strict { get; set; } + + /// + /// Gets or sets the value. + /// + public string Value + { + get => this.encoding.GetString(this.data); + set + { + if (string.IsNullOrEmpty(value)) + { + this.data = Array.Empty(); + } + else + { + int maxLength = this.Tag.MaxLength(); + byte[] valueBytes; + if (this.Strict && value.Length > maxLength) + { + var cappedValue = value.Substring(0, maxLength); + valueBytes = this.encoding.GetBytes(cappedValue); + + // It is still possible that the bytes of the string exceed the limit. + if (valueBytes.Length > maxLength) + { + throw new ArgumentException($"The iptc value exceeds the limit of {maxLength} bytes for the tag {this.Tag}"); + } + } + else + { + valueBytes = this.encoding.GetBytes(value); + } + + this.data = valueBytes; + } + } + } + + /// + /// Gets the length of the value. + /// + public int Length => this.data.Length; + + /// + public IptcValue DeepClone() => new IptcValue(this); + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare this with. + /// True when the specified object is equal to the current . + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + return this.Equals(obj as IptcValue); + } + + /// + /// Determines whether the specified iptc value is equal to the current . + /// + /// The iptc value to compare this with. + /// True when the specified iptc value is equal to the current . + public bool Equals(IptcValue other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (this.Tag != other.Tag) + { + return false; + } + + if (this.data.Length != other.data.Length) + { + return false; + } + + for (int i = 0; i < this.data.Length; i++) + { + if (this.data[i] != other.data[i]) + { + return false; + } + } + + return true; + } + + /// + /// Serves as a hash of this type. + /// + /// A hash code for the current instance. + public override int GetHashCode() => HashCode.Combine(this.data, this.Tag); + + /// + /// Converts this instance to a byte array. + /// + /// A array. + public byte[] ToByteArray() + { + var result = new byte[this.data.Length]; + this.data.CopyTo(result, 0); + return result; + } + + /// + /// Returns a string that represents the current value. + /// + /// A string that represents the current value. + public override string ToString() => this.Value; + + /// + /// Returns a string that represents the current value with the specified encoding. + /// + /// The encoding to use. + /// A string that represents the current value with the specified encoding. + public string ToString(Encoding encoding) + { + Guard.NotNull(encoding, nameof(encoding)); + + return encoding.GetString(this.data); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/README.md b/src/ImageSharp/Metadata/Profiles/IPTC/README.md new file mode 100644 index 0000000000..1217ca0c70 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/README.md @@ -0,0 +1,11 @@ +IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET) + +Information about IPTC can be found here in the following sources: + +- [metacpan.org, APP13-segment](https://metacpan.org/pod/Image::MetaData::JPEG::Structures#Structure-of-a-Photoshop-style-APP13-segment) + +- [iptc.org](https://www.iptc.org/std/photometadata/documentation/userguide/) + +- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf) + +- [Tag Overview](https://exiftool.org/TagNames/IPTC.html) \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs deleted file mode 100644 index 14df385697..0000000000 --- a/src/ImageSharp/PixelFormats/ColorConstants.cs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides useful color definitions. - /// - public static class ColorConstants - { - /// - /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. - /// - public static readonly Rgba32[] WebSafeColors = - { - Rgba32.AliceBlue, - Rgba32.AntiqueWhite, - Rgba32.Aqua, - Rgba32.Aquamarine, - Rgba32.Azure, - Rgba32.Beige, - Rgba32.Bisque, - Rgba32.Black, - Rgba32.BlanchedAlmond, - Rgba32.Blue, - Rgba32.BlueViolet, - Rgba32.Brown, - Rgba32.BurlyWood, - Rgba32.CadetBlue, - Rgba32.Chartreuse, - Rgba32.Chocolate, - Rgba32.Coral, - Rgba32.CornflowerBlue, - Rgba32.Cornsilk, - Rgba32.Crimson, - Rgba32.Cyan, - Rgba32.DarkBlue, - Rgba32.DarkCyan, - Rgba32.DarkGoldenrod, - Rgba32.DarkGray, - Rgba32.DarkGreen, - Rgba32.DarkKhaki, - Rgba32.DarkMagenta, - Rgba32.DarkOliveGreen, - Rgba32.DarkOrange, - Rgba32.DarkOrchid, - Rgba32.DarkRed, - Rgba32.DarkSalmon, - Rgba32.DarkSeaGreen, - Rgba32.DarkSlateBlue, - Rgba32.DarkSlateGray, - Rgba32.DarkTurquoise, - Rgba32.DarkViolet, - Rgba32.DeepPink, - Rgba32.DeepSkyBlue, - Rgba32.DimGray, - Rgba32.DodgerBlue, - Rgba32.Firebrick, - Rgba32.FloralWhite, - Rgba32.ForestGreen, - Rgba32.Fuchsia, - Rgba32.Gainsboro, - Rgba32.GhostWhite, - Rgba32.Gold, - Rgba32.Goldenrod, - Rgba32.Gray, - Rgba32.Green, - Rgba32.GreenYellow, - Rgba32.Honeydew, - Rgba32.HotPink, - Rgba32.IndianRed, - Rgba32.Indigo, - Rgba32.Ivory, - Rgba32.Khaki, - Rgba32.Lavender, - Rgba32.LavenderBlush, - Rgba32.LawnGreen, - Rgba32.LemonChiffon, - Rgba32.LightBlue, - Rgba32.LightCoral, - Rgba32.LightCyan, - Rgba32.LightGoldenrodYellow, - Rgba32.LightGray, - Rgba32.LightGreen, - Rgba32.LightPink, - Rgba32.LightSalmon, - Rgba32.LightSeaGreen, - Rgba32.LightSkyBlue, - Rgba32.LightSlateGray, - Rgba32.LightSteelBlue, - Rgba32.LightYellow, - Rgba32.Lime, - Rgba32.LimeGreen, - Rgba32.Linen, - Rgba32.Magenta, - Rgba32.Maroon, - Rgba32.MediumAquamarine, - Rgba32.MediumBlue, - Rgba32.MediumOrchid, - Rgba32.MediumPurple, - Rgba32.MediumSeaGreen, - Rgba32.MediumSlateBlue, - Rgba32.MediumSpringGreen, - Rgba32.MediumTurquoise, - Rgba32.MediumVioletRed, - Rgba32.MidnightBlue, - Rgba32.MintCream, - Rgba32.MistyRose, - Rgba32.Moccasin, - Rgba32.NavajoWhite, - Rgba32.Navy, - Rgba32.OldLace, - Rgba32.Olive, - Rgba32.OliveDrab, - Rgba32.Orange, - Rgba32.OrangeRed, - Rgba32.Orchid, - Rgba32.PaleGoldenrod, - Rgba32.PaleGreen, - Rgba32.PaleTurquoise, - Rgba32.PaleVioletRed, - Rgba32.PapayaWhip, - Rgba32.PeachPuff, - Rgba32.Peru, - Rgba32.Pink, - Rgba32.Plum, - Rgba32.PowderBlue, - Rgba32.Purple, - Rgba32.RebeccaPurple, - Rgba32.Red, - Rgba32.RosyBrown, - Rgba32.RoyalBlue, - Rgba32.SaddleBrown, - Rgba32.Salmon, - Rgba32.SandyBrown, - Rgba32.SeaGreen, - Rgba32.SeaShell, - Rgba32.Sienna, - Rgba32.Silver, - Rgba32.SkyBlue, - Rgba32.SlateBlue, - Rgba32.SlateGray, - Rgba32.Snow, - Rgba32.SpringGreen, - Rgba32.SteelBlue, - Rgba32.Tan, - Rgba32.Teal, - Rgba32.Thistle, - Rgba32.Tomato, - Rgba32.Transparent, - Rgba32.Turquoise, - Rgba32.Violet, - Rgba32.Wheat, - Rgba32.White, - Rgba32.WhiteSmoke, - Rgba32.Yellow, - Rgba32.YellowGreen - }; - - /// - /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. - /// The hex codes were collected and defined by Nicholas Rougeux - /// - public static readonly Rgba32[] WernerColors = - { - Rgba32.FromHex("#f1e9cd"), - Rgba32.FromHex("#f2e7cf"), - Rgba32.FromHex("#ece6d0"), - Rgba32.FromHex("#f2eacc"), - Rgba32.FromHex("#f3e9ca"), - Rgba32.FromHex("#f2ebcd"), - Rgba32.FromHex("#e6e1c9"), - Rgba32.FromHex("#e2ddc6"), - Rgba32.FromHex("#cbc8b7"), - Rgba32.FromHex("#bfbbb0"), - Rgba32.FromHex("#bebeb3"), - Rgba32.FromHex("#b7b5ac"), - Rgba32.FromHex("#bab191"), - Rgba32.FromHex("#9c9d9a"), - Rgba32.FromHex("#8a8d84"), - Rgba32.FromHex("#5b5c61"), - Rgba32.FromHex("#555152"), - Rgba32.FromHex("#413f44"), - Rgba32.FromHex("#454445"), - Rgba32.FromHex("#423937"), - Rgba32.FromHex("#433635"), - Rgba32.FromHex("#252024"), - Rgba32.FromHex("#241f20"), - Rgba32.FromHex("#281f3f"), - Rgba32.FromHex("#1c1949"), - Rgba32.FromHex("#4f638d"), - Rgba32.FromHex("#383867"), - Rgba32.FromHex("#5c6b8f"), - Rgba32.FromHex("#657abb"), - Rgba32.FromHex("#6f88af"), - Rgba32.FromHex("#7994b5"), - Rgba32.FromHex("#6fb5a8"), - Rgba32.FromHex("#719ba2"), - Rgba32.FromHex("#8aa1a6"), - Rgba32.FromHex("#d0d5d3"), - Rgba32.FromHex("#8590ae"), - Rgba32.FromHex("#3a2f52"), - Rgba32.FromHex("#39334a"), - Rgba32.FromHex("#6c6d94"), - Rgba32.FromHex("#584c77"), - Rgba32.FromHex("#533552"), - Rgba32.FromHex("#463759"), - Rgba32.FromHex("#bfbac0"), - Rgba32.FromHex("#77747f"), - Rgba32.FromHex("#4a475c"), - Rgba32.FromHex("#b8bfaf"), - Rgba32.FromHex("#b2b599"), - Rgba32.FromHex("#979c84"), - Rgba32.FromHex("#5d6161"), - Rgba32.FromHex("#61ac86"), - Rgba32.FromHex("#a4b6a7"), - Rgba32.FromHex("#adba98"), - Rgba32.FromHex("#93b778"), - Rgba32.FromHex("#7d8c55"), - Rgba32.FromHex("#33431e"), - Rgba32.FromHex("#7c8635"), - Rgba32.FromHex("#8e9849"), - Rgba32.FromHex("#c2c190"), - Rgba32.FromHex("#67765b"), - Rgba32.FromHex("#ab924b"), - Rgba32.FromHex("#c8c76f"), - Rgba32.FromHex("#ccc050"), - Rgba32.FromHex("#ebdd99"), - Rgba32.FromHex("#ab9649"), - Rgba32.FromHex("#dbc364"), - Rgba32.FromHex("#e6d058"), - Rgba32.FromHex("#ead665"), - Rgba32.FromHex("#d09b2c"), - Rgba32.FromHex("#a36629"), - Rgba32.FromHex("#a77d35"), - Rgba32.FromHex("#f0d696"), - Rgba32.FromHex("#d7c485"), - Rgba32.FromHex("#f1d28c"), - Rgba32.FromHex("#efcc83"), - Rgba32.FromHex("#f3daa7"), - Rgba32.FromHex("#dfa837"), - Rgba32.FromHex("#ebbc71"), - Rgba32.FromHex("#d17c3f"), - Rgba32.FromHex("#92462f"), - Rgba32.FromHex("#be7249"), - Rgba32.FromHex("#bb603c"), - Rgba32.FromHex("#c76b4a"), - Rgba32.FromHex("#a75536"), - Rgba32.FromHex("#b63e36"), - Rgba32.FromHex("#b5493a"), - Rgba32.FromHex("#cd6d57"), - Rgba32.FromHex("#711518"), - Rgba32.FromHex("#e9c49d"), - Rgba32.FromHex("#eedac3"), - Rgba32.FromHex("#eecfbf"), - Rgba32.FromHex("#ce536b"), - Rgba32.FromHex("#b74a70"), - Rgba32.FromHex("#b7757c"), - Rgba32.FromHex("#612741"), - Rgba32.FromHex("#7a4848"), - Rgba32.FromHex("#3f3033"), - Rgba32.FromHex("#8d746f"), - Rgba32.FromHex("#4d3635"), - Rgba32.FromHex("#6e3b31"), - Rgba32.FromHex("#864735"), - Rgba32.FromHex("#553d3a"), - Rgba32.FromHex("#613936"), - Rgba32.FromHex("#7a4b3a"), - Rgba32.FromHex("#946943"), - Rgba32.FromHex("#c39e6d"), - Rgba32.FromHex("#513e32"), - Rgba32.FromHex("#8b7859"), - Rgba32.FromHex("#9b856b"), - Rgba32.FromHex("#766051"), - Rgba32.FromHex("#453b32") - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 21ec2a3fdc..6d1c03e4bb 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The type implementing this interface public interface IPixel : IPixel, IEquatable - where TSelf : struct, IPixel + where TSelf : unmanaged, IPixel { /// /// Creates a instance for this pixel type. @@ -80,16 +80,28 @@ namespace SixLabors.ImageSharp.PixelFormats void FromBgra32(Bgra32 source); /// - /// Initializes the pixel instance from an value. + /// Initializes the pixel instance from an value. /// - /// The value. - void FromGray8(Gray8 source); + /// The value. + void FromL8(L8 source); /// - /// Initializes the pixel instance from an value. + /// Initializes the pixel instance from an value. /// - /// The value. - void FromGray16(Gray16 source); + /// The value. + void FromL16(L16 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromLa16(La16 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromLa32(La32 source); /// /// Initializes the pixel instance from an value. @@ -121,4 +133,4 @@ namespace SixLabors.ImageSharp.PixelFormats /// The value. void FromRgba64(Rgba64 source); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs index 62dc0fcf59..b2f6261ef5 100644 --- a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs +++ b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs @@ -9,17 +9,17 @@ namespace SixLabors.ImageSharp.PixelFormats public enum PixelAlphaCompositionMode { /// - /// returns the destination over the source. + /// Returns the destination over the source. /// SrcOver = 0, /// - /// returns the source colors. + /// Returns the source colors. /// Src, /// - /// returns the source over the destination. + /// Returns the source over the destination. /// SrcAtop, diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index e94ea452be..f966de63cd 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -4,10 +4,6 @@ // using System; using System.Numerics; -using System.Buffers; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { @@ -23,10 +19,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// to be opaque /// internal static class DefaultPixelBlenders - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - internal class NormalSrc : PixelBlender + /// + /// A pixel blender that implements the "NormalSrc" composition equation. + /// + public class NormalSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -61,7 +60,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplySrc : PixelBlender + /// + /// A pixel blender that implements the "MultiplySrc" composition equation. + /// + public class MultiplySrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -96,7 +98,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddSrc : PixelBlender + /// + /// A pixel blender that implements the "AddSrc" composition equation. + /// + public class AddSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -131,7 +136,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractSrc : PixelBlender + /// + /// A pixel blender that implements the "SubtractSrc" composition equation. + /// + public class SubtractSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -166,7 +174,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenSrc : PixelBlender + /// + /// A pixel blender that implements the "ScreenSrc" composition equation. + /// + public class ScreenSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -201,7 +212,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenSrc : PixelBlender + /// + /// A pixel blender that implements the "DarkenSrc" composition equation. + /// + public class DarkenSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -236,7 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenSrc : PixelBlender + /// + /// A pixel blender that implements the "LightenSrc" composition equation. + /// + public class LightenSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -271,7 +288,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlaySrc : PixelBlender + /// + /// A pixel blender that implements the "OverlaySrc" composition equation. + /// + public class OverlaySrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -306,7 +326,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightSrc : PixelBlender + /// + /// A pixel blender that implements the "HardLightSrc" composition equation. + /// + public class HardLightSrc : PixelBlender { /// /// Gets the static instance of this blender. @@ -341,7 +364,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "NormalSrcAtop" composition equation. + /// + public class NormalSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -376,7 +402,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplySrcAtop : PixelBlender + /// + /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// + public class MultiplySrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -411,7 +440,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "AddSrcAtop" composition equation. + /// + public class AddSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -446,7 +478,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// + public class SubtractSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -481,7 +516,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "ScreenSrcAtop" composition equation. + /// + public class ScreenSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -516,7 +554,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// + public class DarkenSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -551,7 +592,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "LightenSrcAtop" composition equation. + /// + public class LightenSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -586,7 +630,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlaySrcAtop : PixelBlender + /// + /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// + public class OverlaySrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -621,7 +668,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightSrcAtop : PixelBlender + /// + /// A pixel blender that implements the "HardLightSrcAtop" composition equation. + /// + public class HardLightSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -656,7 +706,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalSrcOver : PixelBlender + /// + /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// + public class NormalSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -691,7 +744,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplySrcOver : PixelBlender + /// + /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// + public class MultiplySrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -726,7 +782,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddSrcOver : PixelBlender + /// + /// A pixel blender that implements the "AddSrcOver" composition equation. + /// + public class AddSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -761,7 +820,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractSrcOver : PixelBlender + /// + /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// + public class SubtractSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -796,7 +858,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenSrcOver : PixelBlender + /// + /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// + public class ScreenSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -831,7 +896,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenSrcOver : PixelBlender + /// + /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// + public class DarkenSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -866,7 +934,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenSrcOver : PixelBlender + /// + /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// + public class LightenSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -901,7 +972,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlaySrcOver : PixelBlender + /// + /// A pixel blender that implements the "OverlaySrcOver" composition equation. + /// + public class OverlaySrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -936,7 +1010,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightSrcOver : PixelBlender + /// + /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// + public class HardLightSrcOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -971,7 +1048,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalSrcIn : PixelBlender + /// + /// A pixel blender that implements the "NormalSrcIn" composition equation. + /// + public class NormalSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1006,7 +1086,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplySrcIn : PixelBlender + /// + /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// + public class MultiplySrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1041,7 +1124,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddSrcIn : PixelBlender + /// + /// A pixel blender that implements the "AddSrcIn" composition equation. + /// + public class AddSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1076,7 +1162,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractSrcIn : PixelBlender + /// + /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// + public class SubtractSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1111,7 +1200,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenSrcIn : PixelBlender + /// + /// A pixel blender that implements the "ScreenSrcIn" composition equation. + /// + public class ScreenSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1146,7 +1238,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenSrcIn : PixelBlender + /// + /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// + public class DarkenSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1181,7 +1276,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenSrcIn : PixelBlender + /// + /// A pixel blender that implements the "LightenSrcIn" composition equation. + /// + public class LightenSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1216,7 +1314,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlaySrcIn : PixelBlender + /// + /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// + public class OverlaySrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1251,7 +1352,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightSrcIn : PixelBlender + /// + /// A pixel blender that implements the "HardLightSrcIn" composition equation. + /// + public class HardLightSrcIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -1286,7 +1390,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalSrcOut : PixelBlender + /// + /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// + public class NormalSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1321,7 +1428,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplySrcOut : PixelBlender + /// + /// A pixel blender that implements the "MultiplySrcOut" composition equation. + /// + public class MultiplySrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1356,7 +1466,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddSrcOut : PixelBlender + /// + /// A pixel blender that implements the "AddSrcOut" composition equation. + /// + public class AddSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1391,7 +1504,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractSrcOut : PixelBlender + /// + /// A pixel blender that implements the "SubtractSrcOut" composition equation. + /// + public class SubtractSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1426,7 +1542,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenSrcOut : PixelBlender + /// + /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// + public class ScreenSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1461,7 +1580,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenSrcOut : PixelBlender + /// + /// A pixel blender that implements the "DarkenSrcOut" composition equation. + /// + public class DarkenSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1496,7 +1618,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenSrcOut : PixelBlender + /// + /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// + public class LightenSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1531,7 +1656,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlaySrcOut : PixelBlender + /// + /// A pixel blender that implements the "OverlaySrcOut" composition equation. + /// + public class OverlaySrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1566,7 +1694,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightSrcOut : PixelBlender + /// + /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// + public class HardLightSrcOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -1601,7 +1732,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalDest : PixelBlender + /// + /// A pixel blender that implements the "NormalDest" composition equation. + /// + public class NormalDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1636,7 +1770,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyDest : PixelBlender + /// + /// A pixel blender that implements the "MultiplyDest" composition equation. + /// + public class MultiplyDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1671,7 +1808,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddDest : PixelBlender + /// + /// A pixel blender that implements the "AddDest" composition equation. + /// + public class AddDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1706,7 +1846,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractDest : PixelBlender + /// + /// A pixel blender that implements the "SubtractDest" composition equation. + /// + public class SubtractDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1741,7 +1884,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenDest : PixelBlender + /// + /// A pixel blender that implements the "ScreenDest" composition equation. + /// + public class ScreenDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1776,7 +1922,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenDest : PixelBlender + /// + /// A pixel blender that implements the "DarkenDest" composition equation. + /// + public class DarkenDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1811,7 +1960,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenDest : PixelBlender + /// + /// A pixel blender that implements the "LightenDest" composition equation. + /// + public class LightenDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1846,7 +1998,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayDest : PixelBlender + /// + /// A pixel blender that implements the "OverlayDest" composition equation. + /// + public class OverlayDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1881,7 +2036,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightDest : PixelBlender + /// + /// A pixel blender that implements the "HardLightDest" composition equation. + /// + public class HardLightDest : PixelBlender { /// /// Gets the static instance of this blender. @@ -1916,7 +2074,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalDestAtop : PixelBlender + /// + /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// + public class NormalDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -1951,7 +2112,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyDestAtop : PixelBlender + /// + /// A pixel blender that implements the "MultiplyDestAtop" composition equation. + /// + public class MultiplyDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -1986,7 +2150,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddDestAtop : PixelBlender + /// + /// A pixel blender that implements the "AddDestAtop" composition equation. + /// + public class AddDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2021,7 +2188,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractDestAtop : PixelBlender + /// + /// A pixel blender that implements the "SubtractDestAtop" composition equation. + /// + public class SubtractDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2056,7 +2226,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenDestAtop : PixelBlender + /// + /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// + public class ScreenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2091,7 +2264,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenDestAtop : PixelBlender + /// + /// A pixel blender that implements the "DarkenDestAtop" composition equation. + /// + public class DarkenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2126,7 +2302,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenDestAtop : PixelBlender + /// + /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// + public class LightenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2161,7 +2340,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayDestAtop : PixelBlender + /// + /// A pixel blender that implements the "OverlayDestAtop" composition equation. + /// + public class OverlayDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2196,7 +2378,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightDestAtop : PixelBlender + /// + /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// + public class HardLightDestAtop : PixelBlender { /// /// Gets the static instance of this blender. @@ -2231,7 +2416,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalDestOver : PixelBlender + /// + /// A pixel blender that implements the "NormalDestOver" composition equation. + /// + public class NormalDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2266,7 +2454,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyDestOver : PixelBlender + /// + /// A pixel blender that implements the "MultiplyDestOver" composition equation. + /// + public class MultiplyDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2301,7 +2492,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddDestOver : PixelBlender + /// + /// A pixel blender that implements the "AddDestOver" composition equation. + /// + public class AddDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2336,7 +2530,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractDestOver : PixelBlender + /// + /// A pixel blender that implements the "SubtractDestOver" composition equation. + /// + public class SubtractDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2371,7 +2568,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenDestOver : PixelBlender + /// + /// A pixel blender that implements the "ScreenDestOver" composition equation. + /// + public class ScreenDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2406,7 +2606,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenDestOver : PixelBlender + /// + /// A pixel blender that implements the "DarkenDestOver" composition equation. + /// + public class DarkenDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2441,7 +2644,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenDestOver : PixelBlender + /// + /// A pixel blender that implements the "LightenDestOver" composition equation. + /// + public class LightenDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2476,7 +2682,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayDestOver : PixelBlender + /// + /// A pixel blender that implements the "OverlayDestOver" composition equation. + /// + public class OverlayDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2511,7 +2720,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightDestOver : PixelBlender + /// + /// A pixel blender that implements the "HardLightDestOver" composition equation. + /// + public class HardLightDestOver : PixelBlender { /// /// Gets the static instance of this blender. @@ -2546,7 +2758,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalDestIn : PixelBlender + /// + /// A pixel blender that implements the "NormalDestIn" composition equation. + /// + public class NormalDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2581,7 +2796,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyDestIn : PixelBlender + /// + /// A pixel blender that implements the "MultiplyDestIn" composition equation. + /// + public class MultiplyDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2616,7 +2834,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddDestIn : PixelBlender + /// + /// A pixel blender that implements the "AddDestIn" composition equation. + /// + public class AddDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2651,7 +2872,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractDestIn : PixelBlender + /// + /// A pixel blender that implements the "SubtractDestIn" composition equation. + /// + public class SubtractDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2686,7 +2910,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenDestIn : PixelBlender + /// + /// A pixel blender that implements the "ScreenDestIn" composition equation. + /// + public class ScreenDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2721,7 +2948,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenDestIn : PixelBlender + /// + /// A pixel blender that implements the "DarkenDestIn" composition equation. + /// + public class DarkenDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2756,7 +2986,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenDestIn : PixelBlender + /// + /// A pixel blender that implements the "LightenDestIn" composition equation. + /// + public class LightenDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2791,7 +3024,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayDestIn : PixelBlender + /// + /// A pixel blender that implements the "OverlayDestIn" composition equation. + /// + public class OverlayDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2826,7 +3062,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightDestIn : PixelBlender + /// + /// A pixel blender that implements the "HardLightDestIn" composition equation. + /// + public class HardLightDestIn : PixelBlender { /// /// Gets the static instance of this blender. @@ -2861,7 +3100,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalDestOut : PixelBlender + /// + /// A pixel blender that implements the "NormalDestOut" composition equation. + /// + public class NormalDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2896,7 +3138,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyDestOut : PixelBlender + /// + /// A pixel blender that implements the "MultiplyDestOut" composition equation. + /// + public class MultiplyDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2931,7 +3176,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddDestOut : PixelBlender + /// + /// A pixel blender that implements the "AddDestOut" composition equation. + /// + public class AddDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -2966,7 +3214,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractDestOut : PixelBlender + /// + /// A pixel blender that implements the "SubtractDestOut" composition equation. + /// + public class SubtractDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3001,7 +3252,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenDestOut : PixelBlender + /// + /// A pixel blender that implements the "ScreenDestOut" composition equation. + /// + public class ScreenDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3036,7 +3290,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenDestOut : PixelBlender + /// + /// A pixel blender that implements the "DarkenDestOut" composition equation. + /// + public class DarkenDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3071,7 +3328,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenDestOut : PixelBlender + /// + /// A pixel blender that implements the "LightenDestOut" composition equation. + /// + public class LightenDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3106,7 +3366,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayDestOut : PixelBlender + /// + /// A pixel blender that implements the "OverlayDestOut" composition equation. + /// + public class OverlayDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3141,7 +3404,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightDestOut : PixelBlender + /// + /// A pixel blender that implements the "HardLightDestOut" composition equation. + /// + public class HardLightDestOut : PixelBlender { /// /// Gets the static instance of this blender. @@ -3176,7 +3442,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalClear : PixelBlender + /// + /// A pixel blender that implements the "NormalClear" composition equation. + /// + public class NormalClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3211,7 +3480,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyClear : PixelBlender + /// + /// A pixel blender that implements the "MultiplyClear" composition equation. + /// + public class MultiplyClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3246,7 +3518,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddClear : PixelBlender + /// + /// A pixel blender that implements the "AddClear" composition equation. + /// + public class AddClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3281,7 +3556,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractClear : PixelBlender + /// + /// A pixel blender that implements the "SubtractClear" composition equation. + /// + public class SubtractClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3316,7 +3594,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenClear : PixelBlender + /// + /// A pixel blender that implements the "ScreenClear" composition equation. + /// + public class ScreenClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3351,7 +3632,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenClear : PixelBlender + /// + /// A pixel blender that implements the "DarkenClear" composition equation. + /// + public class DarkenClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3386,7 +3670,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenClear : PixelBlender + /// + /// A pixel blender that implements the "LightenClear" composition equation. + /// + public class LightenClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3421,7 +3708,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayClear : PixelBlender + /// + /// A pixel blender that implements the "OverlayClear" composition equation. + /// + public class OverlayClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3456,7 +3746,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightClear : PixelBlender + /// + /// A pixel blender that implements the "HardLightClear" composition equation. + /// + public class HardLightClear : PixelBlender { /// /// Gets the static instance of this blender. @@ -3491,7 +3784,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class NormalXor : PixelBlender + /// + /// A pixel blender that implements the "NormalXor" composition equation. + /// + public class NormalXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3526,7 +3822,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class MultiplyXor : PixelBlender + /// + /// A pixel blender that implements the "MultiplyXor" composition equation. + /// + public class MultiplyXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3561,7 +3860,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class AddXor : PixelBlender + /// + /// A pixel blender that implements the "AddXor" composition equation. + /// + public class AddXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3596,7 +3898,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class SubtractXor : PixelBlender + /// + /// A pixel blender that implements the "SubtractXor" composition equation. + /// + public class SubtractXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3631,7 +3936,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class ScreenXor : PixelBlender + /// + /// A pixel blender that implements the "ScreenXor" composition equation. + /// + public class ScreenXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3666,7 +3974,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class DarkenXor : PixelBlender + /// + /// A pixel blender that implements the "DarkenXor" composition equation. + /// + public class DarkenXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3701,7 +4012,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class LightenXor : PixelBlender + /// + /// A pixel blender that implements the "LightenXor" composition equation. + /// + public class LightenXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3736,7 +4050,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class OverlayXor : PixelBlender + /// + /// A pixel blender that implements the "OverlayXor" composition equation. + /// + public class OverlayXor : PixelBlender { /// /// Gets the static instance of this blender. @@ -3771,7 +4088,10 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } - internal class HardLightXor : PixelBlender + /// + /// A pixel blender that implements the "HardLightXor" composition equation. + /// + public class HardLightXor : PixelBlender { /// /// Gets the static instance of this blender. diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 66a00975e1..a882de066e 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -14,10 +14,6 @@ // using System; using System.Numerics; -using System.Buffers; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { @@ -33,7 +29,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// to be opaque /// internal static class DefaultPixelBlenders - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { <# @@ -68,9 +64,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders foreach(var blender in blenders) { var blender_composer= $"{blender}{composer}"; - #> - internal class <#= blender_composer#> : PixelBlender + /// + /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// + public class <#= blender_composer#> : PixelBlender { /// /// Gets the static instance of this blender. diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 8b92f95c36..8184f1577b 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -3,8 +3,6 @@ // - -using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -15,8 +13,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders - - + /// + /// Returns the result of the "NormalSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -25,6 +28,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -33,6 +43,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Normal(backdrop, source)); } + /// + /// Returns the result of the "NormalSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -41,14 +58,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Normal(backdrop, source)); } + /// + /// Returns the result of the "NormalSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Normal(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "NormalSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -57,12 +88,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "NormalDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -71,6 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Normal(source, backdrop)); } + /// + /// Returns the result of the "NormalDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -79,14 +131,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Normal(source, backdrop)); } + /// + /// Returns the result of the "NormalDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Normal(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "NormalDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -95,6 +161,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -103,6 +176,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "NormalClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -111,9 +191,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "NormalSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -122,9 +210,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -133,9 +229,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -144,9 +248,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -155,9 +267,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -166,9 +286,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -177,9 +305,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -188,9 +324,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -199,9 +343,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -210,9 +362,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -221,9 +381,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -232,9 +400,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -242,7 +418,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "MultiplySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -251,6 +433,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -259,6 +448,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Multiply(backdrop, source)); } + /// + /// Returns the result of the "MultiplySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -267,14 +463,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Multiply(backdrop, source)); } + /// + /// Returns the result of the "MultiplySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Multiply(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "MultiplySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -283,12 +493,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "MultiplyDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -297,6 +521,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Multiply(source, backdrop)); } + /// + /// Returns the result of the "MultiplyDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -305,14 +536,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Multiply(source, backdrop)); } + /// + /// Returns the result of the "MultiplyDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Multiply(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "MultiplyDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -321,6 +566,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -329,6 +581,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "MultiplyClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -337,9 +596,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "MultiplySrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -348,9 +615,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -359,9 +634,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplySrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -370,9 +653,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplySrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -381,9 +672,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplySrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -392,9 +691,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -403,9 +710,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -414,9 +729,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -425,9 +748,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -436,9 +767,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -447,9 +786,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -458,9 +805,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -468,7 +823,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "AddSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -477,6 +838,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -485,6 +853,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Add(backdrop, source)); } + /// + /// Returns the result of the "AddSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -493,14 +868,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Add(backdrop, source)); } + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Add(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "AddSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -509,12 +898,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "AddDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -523,6 +926,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Add(source, backdrop)); } + /// + /// Returns the result of the "AddDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -531,14 +941,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Add(source, backdrop)); } + /// + /// Returns the result of the "AddDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Add(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "AddDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -547,6 +971,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -555,6 +986,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "AddClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -563,9 +1001,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "AddSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -574,9 +1020,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -585,9 +1039,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -596,9 +1058,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -607,9 +1077,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -618,9 +1096,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -629,9 +1115,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -640,9 +1134,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -651,9 +1153,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -662,9 +1172,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -673,9 +1191,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -684,9 +1210,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -694,7 +1228,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "SubtractSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -703,6 +1243,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -711,6 +1258,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Subtract(backdrop, source)); } + /// + /// Returns the result of the "SubtractSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -719,14 +1273,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Subtract(backdrop, source)); } + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Subtract(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "SubtractSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -735,12 +1303,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "SubtractDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -749,6 +1331,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Subtract(source, backdrop)); } + /// + /// Returns the result of the "SubtractDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -757,14 +1346,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Subtract(source, backdrop)); } + /// + /// Returns the result of the "SubtractDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Subtract(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "SubtractDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -773,6 +1376,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -781,6 +1391,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "SubtractClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -789,9 +1406,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "SubtractSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -800,9 +1425,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -811,9 +1444,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -822,9 +1463,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -833,9 +1482,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -844,9 +1501,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -855,9 +1520,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -866,9 +1539,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -877,9 +1558,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -888,9 +1577,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -899,9 +1596,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -910,9 +1615,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -920,7 +1633,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "ScreenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -929,6 +1648,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -937,6 +1663,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Screen(backdrop, source)); } + /// + /// Returns the result of the "ScreenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -945,14 +1678,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Screen(backdrop, source)); } + /// + /// Returns the result of the "ScreenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Screen(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "ScreenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -961,12 +1708,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "ScreenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -975,6 +1736,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Screen(source, backdrop)); } + /// + /// Returns the result of the "ScreenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -983,14 +1751,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Screen(source, backdrop)); } + /// + /// Returns the result of the "ScreenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Screen(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "ScreenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -999,6 +1781,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -1007,6 +1796,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -1015,9 +1811,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "ScreenSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1026,9 +1830,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1037,9 +1849,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1048,9 +1868,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1059,9 +1887,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1070,9 +1906,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1081,9 +1925,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1092,9 +1944,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1103,9 +1963,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1114,9 +1982,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1125,9 +2001,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1136,9 +2020,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1146,7 +2038,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "DarkenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -1155,6 +2053,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1163,6 +2068,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Darken(backdrop, source)); } + /// + /// Returns the result of the "DarkenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1171,14 +2083,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Darken(backdrop, source)); } + /// + /// Returns the result of the "DarkenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Darken(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "DarkenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1187,12 +2113,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "DarkenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1201,6 +2141,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Darken(source, backdrop)); } + /// + /// Returns the result of the "DarkenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1209,14 +2156,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Darken(source, backdrop)); } + /// + /// Returns the result of the "DarkenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Darken(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "DarkenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1225,6 +2186,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -1233,6 +2201,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "DarkenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -1241,9 +2216,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "DarkenSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1252,9 +2235,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1263,9 +2254,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1274,9 +2273,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1285,9 +2292,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1296,9 +2311,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1307,9 +2330,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1318,9 +2349,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1329,9 +2368,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1340,9 +2387,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1351,9 +2406,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1362,9 +2425,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1372,7 +2443,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "LightenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -1381,6 +2458,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1389,6 +2473,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Lighten(backdrop, source)); } + /// + /// Returns the result of the "LightenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1397,14 +2488,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Lighten(backdrop, source)); } + /// + /// Returns the result of the "LightenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Lighten(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1413,12 +2518,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "LightenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1427,6 +2546,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Lighten(source, backdrop)); } + /// + /// Returns the result of the "LightenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1435,14 +2561,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Lighten(source, backdrop)); } + /// + /// Returns the result of the "LightenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Lighten(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "LightenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1451,6 +2591,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -1459,6 +2606,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "LightenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -1467,9 +2621,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "LightenSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1478,9 +2640,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1489,9 +2659,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1500,9 +2678,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1511,9 +2697,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1522,9 +2716,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1533,9 +2735,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1544,9 +2754,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1555,9 +2773,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1566,9 +2792,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1577,9 +2811,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1588,9 +2830,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1598,7 +2848,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "OverlaySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -1607,6 +2863,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1615,6 +2878,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, Overlay(backdrop, source)); } + /// + /// Returns the result of the "OverlaySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1623,14 +2893,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, Overlay(backdrop, source)); } + /// + /// Returns the result of the "OverlaySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, Overlay(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "OverlaySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1639,12 +2923,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "OverlayDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1653,6 +2951,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, Overlay(source, backdrop)); } + /// + /// Returns the result of the "OverlayDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1661,14 +2966,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, Overlay(source, backdrop)); } + /// + /// Returns the result of the "OverlayDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, Overlay(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "OverlayDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1677,6 +2996,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -1685,6 +3011,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -1693,9 +3026,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "OverlaySrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1704,9 +3045,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1715,9 +3064,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlaySrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1726,9 +3083,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlaySrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1737,9 +3102,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlaySrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1748,9 +3121,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1759,9 +3140,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1770,9 +3159,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1781,9 +3178,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1792,9 +3197,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1803,9 +3216,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1814,9 +3235,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1824,7 +3253,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - + /// + /// Returns the result of the "HardLightSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) { @@ -1833,6 +3268,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1841,6 +3283,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, HardLight(backdrop, source)); } + /// + /// Returns the result of the "HardLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1849,14 +3298,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, HardLight(backdrop, source)); } + /// + /// Returns the result of the "HardLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, HardLight(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "HardLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1865,12 +3328,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "HardLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -1879,6 +3356,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, HardLight(source, backdrop)); } + /// + /// Returns the result of the "HardLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -1887,14 +3371,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, HardLight(source, backdrop)); } + /// + /// Returns the result of the "HardLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, HardLight(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "HardLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -1903,6 +3401,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) { @@ -1911,6 +3416,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "HardLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) { @@ -1919,9 +3431,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Clear(backdrop, source); } + /// + /// Returns the result of the "HardLightSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1930,9 +3450,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1941,9 +3469,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1952,9 +3488,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1963,9 +3507,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1974,9 +3526,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1985,9 +3545,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1996,9 +3564,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2007,9 +3583,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2018,9 +3602,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2029,9 +3621,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2040,9 +3640,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index f591ee873d..be2beb2f8e 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -17,8 +17,6 @@ // Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when // AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit. #> - -using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -26,9 +24,14 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { internal static partial class PorterDuffFunctions { - <# void GeneratePixelBlenders(string blender) { #> - + /// + /// Returns the result of the "<#=blender#>Src" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) { @@ -37,6 +40,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return source; } + /// + /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -45,6 +55,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(backdrop, source, <#=blender#>(backdrop, source)); } + /// + /// Returns the result of the "<#=blender#>SrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -53,14 +70,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(backdrop, source, <#=blender#>(backdrop, source)); } + /// + /// Returns the result of the "<#=blender#>SrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(backdrop, source, <#=blender#>(backdrop, source)); + return In(backdrop, source); } + /// + /// Returns the result of the "<#=blender#>SrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -69,12 +100,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(backdrop, source); } + /// + /// Returns the result of the "<#=blender#>Dest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } + /// + /// Returns the result of the "<#=blender#>DestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) { @@ -83,6 +128,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Atop(source, backdrop, <#=blender#>(source, backdrop)); } + /// + /// Returns the result of the "<#=blender#>DestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) { @@ -91,14 +143,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Over(source, backdrop, <#=blender#>(source, backdrop)); } + /// + /// Returns the result of the "<#=blender#>DestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - return In(source, backdrop, <#=blender#>(source, backdrop)); + return In(source, backdrop); } + /// + /// Returns the result of the "<#=blender#>DestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) { @@ -107,6 +173,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Out(source, backdrop); } + /// + /// Returns the result of the "<#=blender#>Xor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) { @@ -115,6 +188,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Xor(backdrop, source); } + /// + /// Returns the result of the "<#=blender#>Clear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) { @@ -127,9 +207,17 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders <# void GenerateGenericPixelBlender(string blender, string composer) { #> + /// + /// Returns the result of the "<#=blender#><#=composer#>" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 9111520a02..97b4458aff 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -21,11 +21,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders internal static partial class PorterDuffFunctions { /// - /// Source over backdrop + /// Returns the result of the "Normal" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Normal(Vector4 backdrop, Vector4 source) { @@ -33,11 +33,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Source multiplied by backdrop + /// Returns the result of the "Multiply" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Multiply(Vector4 backdrop, Vector4 source) { @@ -45,11 +45,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Source added to backdrop + /// Returns the result of the "Add" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Add(Vector4 backdrop, Vector4 source) { @@ -57,11 +57,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Source subtracted from backdrop + /// Returns the result of the "Subtract" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Subtract(Vector4 backdrop, Vector4 source) { @@ -69,11 +69,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Complement of source multiplied by the complement of backdrop + /// Returns the result of the "Screen" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Screen(Vector4 backdrop, Vector4 source) { @@ -81,11 +81,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Per element, chooses the smallest value of source and backdrop + /// Returns the result of the "Darken" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Darken(Vector4 backdrop, Vector4 source) { @@ -93,11 +93,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Per element, chooses the largest value of source and backdrop + /// Returns the result of the "Lighten" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Lighten(Vector4 backdrop, Vector4 source) { @@ -105,11 +105,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Overlays source over backdrop + /// Returns the result of the "Overlay" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Overlay(Vector4 backdrop, Vector4 source) { @@ -121,11 +121,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - /// Hard light effect + /// Returns the result of the "HardLight" compositing equation. /// - /// Backdrop color - /// Source color - /// Output color + /// The backdrop vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 HardLight(Vector4 backdrop, Vector4 source) { @@ -145,22 +145,29 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float OverlayValueFunction(float backdrop, float source) { - return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop)); } + /// + /// Returns the result of the "Over" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Over(Vector4 dst, Vector4 src, Vector4 blend) + public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend) { // calculate weights - float blendW = dst.W * src.W; - float dstW = dst.W - blendW; - float srcW = src.W - blendW; + float blendW = destination.W * source.W; + float dstW = destination.W - blendW; + float srcW = source.W - blendW; // calculate final alpha - float alpha = dstW + srcW + blendW; + float alpha = dstW + source.W; // calculate final color - Vector4 color = (dst * dstW) + (src * srcW) + (blend * blendW); + Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW); // unpremultiply color /= MathF.Max(alpha, Constants.Epsilon); @@ -169,18 +176,25 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return color; } + /// + /// Returns the result of the "Atop" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend) + public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend) { // calculate weights - float blendW = dst.W * src.W; - float dstW = dst.W - blendW; + float blendW = destination.W * source.W; + float dstW = destination.W - blendW; // calculate final alpha - float alpha = dstW + blendW; + float alpha = destination.W; // calculate final color - Vector4 color = (dst * dstW) + (blend * blendW); + Vector4 color = (destination * dstW) + (blend * blendW); // unpremultiply color /= MathF.Max(alpha, Constants.Epsilon); @@ -189,38 +203,56 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return color; } + /// + /// Returns the result of the "In" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend) + public static Vector4 In(Vector4 destination, Vector4 source) { - float alpha = dst.W * src.W; + float alpha = destination.W * source.W; - Vector4 color = src * alpha; // premultiply + Vector4 color = source * alpha; // premultiply color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply color.W = alpha; return color; } + /// + /// Returns the result of the "Out" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Out(Vector4 dst, Vector4 src) + public static Vector4 Out(Vector4 destination, Vector4 source) { - float alpha = (1 - dst.W) * src.W; + float alpha = (1 - destination.W) * source.W; - Vector4 color = src * alpha; // premultiply + Vector4 color = source * alpha; // premultiply color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply color.W = alpha; return color; } + /// + /// Returns the result of the "XOr" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Xor(Vector4 dst, Vector4 src) + public static Vector4 Xor(Vector4 destination, Vector4 source) { - float srcW = 1 - dst.W; - float dstW = 1 - src.W; + float srcW = 1 - destination.W; + float dstW = 1 - source.W; - float alpha = (src.W * srcW) + (dst.W * dstW); - Vector4 color = (src.W * src * srcW) + (dst.W * dst * dstW); + float alpha = (source.W * srcW) + (destination.W * dstW); + Vector4 color = (source.W * source * srcW) + (destination.W * destination * dstW); // unpremultiply color /= MathF.Max(alpha, Constants.Epsilon); @@ -235,4 +267,4 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return Vector4.Zero; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 77bee23937..244aba7de6 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats @@ -13,8 +12,8 @@ namespace SixLabors.ImageSharp.PixelFormats /// Abstract base class for calling pixel composition functions /// /// The type of the pixel - internal abstract class PixelBlender - where TPixel : struct, IPixel + public abstract class PixelBlender + where TPixel : unmanaged, IPixel { /// /// Blend 2 pixels together. @@ -23,64 +22,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// The source color. /// /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// - /// The final pixel value after composition + /// The final pixel value after composition. public abstract TPixel Blend(TPixel background, TPixel source, float amount); - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount); - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount); - - /// - /// Blends 2 rows together - /// - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - { - this.Blend(configuration, destination, background, source, amount); - } - /// /// Blends 2 rows together /// @@ -90,20 +36,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// the background span /// the source span /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// public void Blend( Configuration configuration, Span destination, ReadOnlySpan background, ReadOnlySpan source, - ReadOnlySpan amount) - where TPixelSrc : struct, IPixel + float amount) + where TPixelSrc : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); using (IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(destination.Length * 3)) @@ -124,6 +70,25 @@ namespace SixLabors.ImageSharp.PixelFormats } } + /// + /// Blends 2 rows together + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + => this.Blend(configuration, destination, background, source, amount); + /// /// Blends 2 rows together /// @@ -133,20 +98,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// the background span /// the source span /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// public void Blend( Configuration configuration, Span destination, ReadOnlySpan background, ReadOnlySpan source, - float amount) - where TPixelSrc : struct, IPixel + ReadOnlySpan amount) + where TPixelSrc : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); using (IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(destination.Length * 3)) @@ -166,5 +131,37 @@ namespace SixLabors.ImageSharp.PixelFormats PixelOperations.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); } } + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount); + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs index 2f9abce5d8..eb64e532ea 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// knowing the pixel type. /// [Flags] - internal enum PixelConversionModifiers + public enum PixelConversionModifiers { /// /// No special operation is selected diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs new file mode 100644 index 0000000000..444221d88a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -0,0 +1,167 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 8-bit normalized alpha value. + /// + /// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. + /// + /// + public struct A8 : IPixel, IPackedVector + { + /// + /// Initializes a new instance of the struct. + /// + /// The alpha component. + public A8(byte alpha) => this.PackedValue = alpha; + + /// + /// Initializes a new instance of the struct. + /// + /// The alpha component. + public A8(float alpha) => this.PackedValue = Pack(alpha); + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(A8 left, A8 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(A8 left, A8 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest = default; + dest.A = this.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Compares an object with the packed vector. + /// + /// The object to compare. + /// True if the object is equal to the packed vector. + public override readonly bool Equals(object obj) => obj is A8 other && this.Equals(other); + + /// + /// Compares another A8 packed vector with the packed vector. + /// + /// The A8 packed vector to compare. + /// True if the packed vectors are equal. + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(A8 other) => this.PackedValue.Equals(other.PackedValue); + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override readonly string ToString() => $"A8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a byte. + /// + /// The float containing the value to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Pack(float alpha) => (byte)Math.Round(alpha.Clamp(0, 1F) * 255F); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs deleted file mode 100644 index cd864b5455..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Packed pixel type containing a single 8 bit normalized W values. - /// - /// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. - /// - /// - public struct Alpha8 : IPixel, IPackedVector - { - /// - /// Initializes a new instance of the struct. - /// - /// The alpha component. - public Alpha8(byte alpha) => this.PackedValue = alpha; - - /// - /// Initializes a new instance of the struct. - /// - /// The alpha component. - public Alpha8(float alpha) => this.PackedValue = Pack(alpha); - - /// - public byte PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Alpha8 left, Alpha8 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Alpha8 left, Alpha8 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest = default; - dest.A = this.PackedValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Compares an object with the packed vector. - /// - /// The object to compare. - /// True if the object is equal to the packed vector. - public override bool Equals(object obj) => obj is Alpha8 other && this.Equals(other); - - /// - /// Compares another Alpha8 packed vector with the packed vector. - /// - /// The Alpha8 packed vector to compare. - /// True if the packed vectors are equal. - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Alpha8 other) => this.PackedValue.Equals(other.PackedValue); - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override string ToString() => $"Alpha8({this.PackedValue})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - /// Packs a into a byte. - /// - /// The float containing the value to pack. - /// The containing the packed values. - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Pack(float alpha) => (byte)Math.Round(alpha.Clamp(0, 1F) * 255F); - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index 8981c87458..52f6bcaa19 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Argb { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -138,7 +138,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => this.Argb; + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => this.Argb; + + [MethodImpl(InliningOptions.ShortMethod)] set => this.Argb = value; } @@ -181,7 +184,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -189,7 +192,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -197,7 +200,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -229,7 +232,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -239,7 +242,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -248,6 +251,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = byte.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -299,21 +323,21 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); + public override readonly bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Argb32 other) => this.Argb == other.Argb; + public readonly bool Equals(Argb32 other) => this.Argb == other.Argb; /// /// Gets a string representation of the packed vector. /// /// A string representation of the packed vector. - public override string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.Argb.GetHashCode(); + public override readonly int GetHashCode() => this.Argb.GetHashCode(); /// /// Packs the four floats into a color. @@ -349,7 +373,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; @@ -357,4 +381,4 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = (byte)vector.W; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index a0b059dfce..0a2f58409f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -157,6 +157,25 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = rgb; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -200,16 +219,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// - public override bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); /// - public override string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; + public override readonly string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 9e42de388c..2659689bda 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector3(), 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector3(), 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -101,11 +101,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -136,7 +144,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() + public readonly Vector3 ToVector3() { return new Vector3( ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), @@ -145,14 +153,14 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector3(); return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); @@ -160,7 +168,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector3 vector) @@ -172,4 +180,4 @@ namespace SixLabors.ImageSharp.PixelFormats | ((int)Math.Round(vector.Z * 31F) & 0x1F)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index ea7a961885..40c187eb27 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Bgra { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => this.Bgra; + readonly get => this.Bgra; set => this.Bgra = value; } @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -204,6 +204,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = byte.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) @@ -255,16 +276,16 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); /// - public bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); + public readonly bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); /// - public override int GetHashCode() => this.Bgra.GetHashCode(); + public override readonly int GetHashCode() => this.Bgra.GetHashCode(); /// - public override string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; + public override readonly string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; /// /// Packs a into a color. @@ -275,7 +296,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; @@ -283,4 +304,4 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = (byte)vector.W; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 6fcac62917..bbbf9145c7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float Max = 1 / 15F; @@ -104,11 +104,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -134,14 +142,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Bgra4444({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); @@ -149,16 +157,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); return (ushort)((((int)Math.Round(vector.W * 15F) & 0x0F) << 12) | (((int)Math.Round(vector.X * 15F) & 0x0F) << 8) | (((int)Math.Round(vector.Y * 15F) & 0x0F) << 4) | ((int)Math.Round(vector.Z * 15F) & 0x0F)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index abb3eb19e5..d10d10b477 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. /// /// - public struct Bgra5551 : IPixel, IPackedVector + public partial struct Bgra5551 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( ((this.PackedValue >> 10) & 0x1F) / 31F, @@ -105,11 +105,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -139,10 +147,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Bgra5551({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); @@ -150,12 +158,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); return (ushort)( (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) @@ -163,4 +171,4 @@ namespace SixLabors.ImageSharp.PixelFormats | (((int)Math.Round(vector.W) & 0x1) << 15)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index ab8a13c166..49b4f4138e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4() / 255F; + public readonly Vector4 ToScaledVector4() => this.ToVector4() / 255F; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( this.PackedValue & 0xFF, @@ -101,11 +101,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -135,18 +143,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); + public override readonly bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Byte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -163,7 +171,7 @@ namespace SixLabors.ImageSharp.PixelFormats const float Max = 255F; // Clamp the value between min and max values - vector = Vector4.Clamp(vector, Vector4.Zero, new Vector4(Max)); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, new Vector4(Max)); uint byte4 = (uint)Math.Round(vector.X) & 0xFF; uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; @@ -173,4 +181,4 @@ namespace SixLabors.ImageSharp.PixelFormats return byte4 | byte3 | byte2 | byte1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs index 5bacc6747d..83bc46d8ec 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs @@ -24,42 +24,42 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -69,13 +69,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -84,13 +84,13 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -100,13 +100,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -116,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -134,49 +134,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -188,13 +224,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -206,13 +242,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -222,8 +258,26 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromArgb32(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs index 4b2b3a02e4..8f21ef2d4e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs @@ -11,7 +11,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - namespace SixLabors.ImageSharp.PixelFormats { /// @@ -25,43 +24,43 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -73,13 +72,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -91,49 +90,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -145,13 +180,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -163,13 +198,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -181,13 +216,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -197,15 +232,32 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgr24(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { PixelOperations.Instance.ToBgr24(configuration, sourcePixels, destinationPixels); } - } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs index 6074e19778..58a68bd031 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs @@ -24,42 +24,42 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -69,13 +69,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -84,13 +84,13 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -100,13 +100,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -116,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -134,49 +134,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -188,13 +224,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -206,13 +242,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -222,8 +258,26 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgra32(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.cs new file mode 100644 index 0000000000..4def59ea11 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Bgra5551 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgra5551(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.tt new file mode 100644 index 0000000000..c4f2fc8bdd --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra5551.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Bgra5551 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Bgra5551"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs deleted file mode 100644 index c30dfe0c02..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -// - -using SixLabors.ImageSharp.PixelFormats.Utils; -using System; -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides optimized overrides for bulk operations. - /// - public partial struct Gray16 - { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - /// - internal override void FromGray16(Configuration configuration, ReadOnlySpan source, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); - - source.CopyTo(destPixels); - } - - /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - sourcePixels.CopyTo(destPixels); - } - - - /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - - /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray16(sp); - } - } - /// - internal override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - PixelOperations.Instance.ToGray16(configuration, sourcePixels, destinationPixels); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt deleted file mode 100644 index ac484bdbc0..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt +++ /dev/null @@ -1,19 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides optimized overrides for bulk operations. - /// - public partial struct Gray16 - { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Gray16"); #> - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs deleted file mode 100644 index d37a253097..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -// - -using SixLabors.ImageSharp.PixelFormats.Utils; -using System; -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides optimized overrides for bulk operations. - /// - public partial struct Gray8 - { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - /// - internal override void FromGray8(Configuration configuration, ReadOnlySpan source, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); - - source.CopyTo(destPixels); - } - - /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - sourcePixels.CopyTo(destPixels); - } - - - /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - - /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - - ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); - - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromGray8(sp); - } - } - /// - internal override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - PixelOperations.Instance.ToGray8(configuration, sourcePixels, destinationPixels); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt deleted file mode 100644 index ffa15af9dc..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt +++ /dev/null @@ -1,19 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides optimized overrides for bulk operations. - /// - public partial struct Gray8 - { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Gray8"); #> - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.cs new file mode 100644 index 0000000000..dd9a3ac10f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct L16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToL16(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.tt new file mode 100644 index 0000000000..937902346f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L16.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct L16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("L16"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.cs new file mode 100644 index 0000000000..6a5ec6971f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct L8 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToL8(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.tt new file mode 100644 index 0000000000..d2e802a190 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/L8.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct L8 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("L8"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.cs new file mode 100644 index 0000000000..66e8d7dc07 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct La16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToLa16(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.tt new file mode 100644 index 0000000000..5d6661631d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La16.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct La16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("La16"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.cs new file mode 100644 index 0000000000..e3f0330083 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct La32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels); + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels); + } + + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToLa32(configuration, sourcePixels, destinationPixels); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.tt new file mode 100644 index 0000000000..e2fb4867a9 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/La32.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct La32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("La32"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs index e5f648fcb7..face124a6e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs @@ -24,43 +24,43 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + public override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -72,13 +72,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -90,13 +90,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -108,49 +108,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -162,13 +198,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -180,13 +216,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -196,8 +232,26 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb24(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs index d3b2bc6f48..6828079c23 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs @@ -24,32 +24,32 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -61,13 +61,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -97,49 +97,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -151,13 +187,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -169,13 +205,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -185,8 +221,26 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb48(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs index 0da2bf2449..6437b04091 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs @@ -24,31 +24,31 @@ namespace SixLabors.ImageSharp.PixelFormats internal partial class PixelOperations : PixelOperations { /// - internal override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -58,13 +58,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -73,13 +73,13 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -89,13 +89,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -123,49 +123,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -177,13 +213,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -195,13 +231,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -211,8 +247,26 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba32(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs index 83f5cadd6d..c48493faf0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs @@ -24,32 +24,32 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destPixels) + public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } /// - internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -61,13 +61,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -97,49 +97,85 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } } /// - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } } /// - internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -151,13 +187,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -169,13 +205,13 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -185,8 +221,26 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba64(sp); } } + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude index 17e9469f31..076db616b2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude @@ -1,4 +1,4 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> @@ -15,7 +15,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; <#+ - static readonly string[] CommonPixelTypes = { "Argb32", "Bgr24", "Bgra32", "Gray8", "Gray16", "Rgb24", "Rgba32", "Rgb48", "Rgba64" }; + static readonly string[] CommonPixelTypes = { "Argb32", "Bgr24", "Bgra32", "L8", "L16", "La16", "La32", "Rgb24", "Rgba32", "Rgb48", "Rgba64", "Bgra5551" }; static readonly string[] Optimized32BitTypes = { "Rgba32", "Argb32", "Bgra32" }; @@ -26,7 +26,7 @@ using System.Runtime.InteropServices; { #> /// - internal override void From( + public override void From( Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destinationPixels) @@ -40,21 +40,21 @@ using System.Runtime.InteropServices; { #> /// - internal override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destPixels) + public override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destPixels); + source.CopyTo(destinationPixels); } /// - internal override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destPixels) + public override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destPixels); + sourcePixels.CopyTo(destinationPixels); } <#+ @@ -65,13 +65,13 @@ using System.Runtime.InteropServices; #> /// - internal override void To<#=toPixelType#>(Configuration configuration, ReadOnlySpan<<#=fromPixelType#>> sourcePixels, Span<<#=toPixelType#>> destPixels) + public override void To<#=toPixelType#>(Configuration configuration, ReadOnlySpan<<#=fromPixelType#>> sourcePixels, Span<<#=toPixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destPixels); + ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -88,13 +88,13 @@ using System.Runtime.InteropServices; { #> /// - internal override void To<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=thisPixelType#>> sourcePixels, Span<<#=otherPixelType#>> destPixels) + public override void To<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=thisPixelType#>> sourcePixels, Span<<#=otherPixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As<<#=thisPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As<<#=otherPixelType#>, uint>(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As<<#=otherPixelType#>, uint>(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -104,13 +104,13 @@ using System.Runtime.InteropServices; } /// - internal override void From<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=otherPixelType#>> sourcePixels, Span<<#=thisPixelType#>> destPixels) + public override void From<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=otherPixelType#>> sourcePixels, Span<<#=thisPixelType#>> destinationPixels) { Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref uint sourceRef = ref Unsafe.As<<#=otherPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels)); - ref uint destRef = ref Unsafe.As<<#=thisPixelType#>, uint>(ref MemoryMarshal.GetReference(destPixels)); + ref uint destRef = ref Unsafe.As<<#=thisPixelType#>, uint>(ref MemoryMarshal.GetReference(destinationPixels)); for (int i = 0; i < sourcePixels.Length; i++) { @@ -130,13 +130,13 @@ using System.Runtime.InteropServices; } #> /// - internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers) + public override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destinationPixels, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(<#=removeTheseModifiers#>)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(<#=removeTheseModifiers#>)); } /// - internal override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + public override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors, PixelConversionModifiers modifiers) { Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(<#=removeTheseModifiers#>)); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs deleted file mode 100644 index b5bd14d9f1..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Packed pixel type containing a single 16 bit normalized gray values. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// - /// - public partial struct Gray16 : IPixel, IPackedVector - { - private const float Max = ushort.MaxValue; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component - public Gray16(ushort luminance) => this.PackedValue = luminance; - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Gray16 left, Gray16 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Gray16 left, Gray16 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() - { - float scaled = this.PackedValue / Max; - return new Vector4(scaled, scaled, scaled, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.PackedValue = ImageMaths.Get16BitBT709Luminance( - ImageMaths.UpscaleFrom8BitTo16Bit(source.R), - ImageMaths.UpscaleFrom8BitTo16Bit(source.G), - ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.PackedValue = ImageMaths.Get16BitBT709Luminance( - ImageMaths.UpscaleFrom8BitTo16Bit(source.R), - ImageMaths.UpscaleFrom8BitTo16Bit(source.G), - ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.PackedValue = ImageMaths.Get16BitBT709Luminance( - ImageMaths.UpscaleFrom8BitTo16Bit(source.R), - ImageMaths.UpscaleFrom8BitTo16Bit(source.G), - ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.PackedValue = source.PackedValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.PackedValue = ImageMaths.Get16BitBT709Luminance( - ImageMaths.UpscaleFrom8BitTo16Bit(source.R), - ImageMaths.UpscaleFrom8BitTo16Bit(source.G), - ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.PackedValue = ImageMaths.Get16BitBT709Luminance( - ImageMaths.UpscaleFrom8BitTo16Bit(source.R), - ImageMaths.UpscaleFrom8BitTo16Bit(source.G), - ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(this.PackedValue); - dest.R = rgb; - dest.G = rgb; - dest.B = rgb; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); - - /// - public override bool Equals(object obj) => obj is Gray16 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Gray16 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() => $"Gray16({this.PackedValue})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; - this.PackedValue = ImageMaths.Get16BitBT709Luminance( - vector.X, - vector.Y, - vector.Z); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs deleted file mode 100644 index ac67c9d170..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Packed pixel type containing a single 8 bit normalized gray values. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// - /// - public partial struct Gray8 : IPixel, IPackedVector - { - private static readonly Vector4 MaxBytes = new Vector4(255F); - private static readonly Vector4 Half = new Vector4(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - public Gray8(byte luminance) => this.PackedValue = luminance; - - /// - public byte PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Gray8 left, Gray8 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Gray8 left, Gray8 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() - { - float rgb = this.PackedValue / 255F; - return new Vector4(rgb, rgb, rgb, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.PackedValue = source.PackedValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.PackedValue; - dest.G = this.PackedValue; - dest.B = this.PackedValue; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - => this.PackedValue = ImageMaths.Get8BitBT709Luminance( - ImageMaths.DownScaleFrom16BitTo8Bit(source.R), - ImageMaths.DownScaleFrom16BitTo8Bit(source.G), - ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - => this.PackedValue = ImageMaths.Get8BitBT709Luminance( - ImageMaths.DownScaleFrom16BitTo8Bit(source.R), - ImageMaths.DownScaleFrom16BitTo8Bit(source.G), - ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); - - /// - public override bool Equals(object obj) => obj is Gray8 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Gray8 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() => $"Gray8({this.PackedValue})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index 580cc5399b..977df78b8f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { float single = this.ToSingle() + 1F; single /= 2F; @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -94,11 +94,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -128,20 +136,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); + public readonly float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); /// - public override bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); + public override readonly string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 8854f16501..1ecaa05da1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { var vector = this.ToVector2(); return new Vector4(vector.X, vector.Y, 0F, 1F); @@ -105,11 +105,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -139,7 +147,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { Vector2 vector; vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); @@ -148,14 +156,14 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -163,7 +171,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(float x, float y) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 3b30ebd1e6..35822779fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( HalfTypeHelper.Unpack((ushort)this.PackedValue), @@ -113,11 +113,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -143,14 +151,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"HalfVector4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -158,7 +166,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// /// Packs a into a . @@ -175,4 +183,4 @@ namespace SixLabors.ImageSharp.PixelFormats return num4 | num3 | num2 | num1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs new file mode 100644 index 0000000000..815ae6a4e3 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 16-bit normalized luminance value. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + public partial struct L16 : IPixel, IPackedVector + { + private const float Max = ushort.MaxValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component + public L16(ushort luminance) => this.PackedValue = luminance; + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(L16 left, L16 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(L16 left, L16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float scaled = this.PackedValue / Max; + return new Vector4(scaled, scaled, scaled, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = source.L; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(this.PackedValue); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + public override readonly bool Equals(object obj) => obj is L16 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L16({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs new file mode 100644 index 0000000000..37a028db25 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 8-bit normalized luminance value. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + public partial struct L8 : IPixel, IPackedVector + { + private static readonly Vector4 MaxBytes = new Vector4(255F); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + public L8(byte luminance) => this.PackedValue = luminance; + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(L8 left, L8 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(L8 left, L8 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float rgb = this.PackedValue / 255F; + return new Vector4(rgb, rgb, rgb, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = source.L; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + => this.PackedValue = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + => this.PackedValue = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + /// + public override readonly bool Equals(object obj) => obj is L8 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); + this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs new file mode 100644 index 0000000000..104c2be45a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing two 8-bit normalized values representing luminance and alpha. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// + /// + [StructLayout(LayoutKind.Explicit)] + public partial struct La16 : IPixel, IPackedVector + { + private static readonly Vector4 MaxBytes = new Vector4(255F); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Gets or sets the luminance component. + /// + [FieldOffset(0)] + public byte L; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(1)] + public byte A; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + /// The alpha componant. + public La16(byte l, byte a) + { + this.L = l; + this.A = a; + } + + /// + public ushort PackedValue + { + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(La16 left, La16 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(La16 left, La16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is La16 other && this.Equals(other); + + /// + public override readonly string ToString() => $"La16({this.L}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.L = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.L = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.L = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.L = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.L = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + public void FromRgba64(Rgba64 source) + { + this.L = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.L; + dest.G = this.L; + dest.B = this.L; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + const float Max = 255F; + float rgb = this.L / Max; + return new Vector4(rgb, rgb, rgb, this.A / Max); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); + this.L = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); + this.A = (byte)vector.W; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs new file mode 100644 index 0000000000..98a6cdae49 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing two 16-bit normalized values representing luminance and alpha. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// + /// + [StructLayout(LayoutKind.Explicit)] + public partial struct La32 : IPixel, IPackedVector + { + private const float Max = ushort.MaxValue; + + /// + /// Gets or sets the luminance component. + /// + [FieldOffset(0)] + public ushort L; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(2)] + public ushort A; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + /// The alpha componant. + public La32(ushort l, ushort a) + { + this.L = l; + this.A = a; + } + + /// + public uint PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(La32 left, La32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(La32 left, La32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is La32 other && this.Equals(other); + + /// + public override readonly string ToString() => $"La32({this.L}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.L = source.PackedValue; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.L = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.L = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.L = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.L = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.L = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(this.L); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = ImageMaths.DownScaleFrom16BitTo8Bit(this.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float scaled = this.L / Max; + return new Vector4(scaled, scaled, scaled, this.A / Max); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; + this.L = ImageMaths.Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); + + this.A = (ushort)MathF.Round(vector.W); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index a24e725692..54effcb227 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -60,20 +60,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - var scaled = new Vector2(vector.X, vector.Y) * 2F; + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; scaled -= Vector2.One; this.PackedValue = Pack(scaled); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -115,11 +115,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -143,7 +151,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { return new Vector2( (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, @@ -151,18 +159,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 73a3d32625..a7b350d557 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, @@ -116,11 +116,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -146,18 +154,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"NormalizedByte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -166,7 +174,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, MinusOne, Vector4.One) * Half; + vector = Vector4Utilities.FastClamp(vector, MinusOne, Vector4.One) * Half; uint byte4 = ((uint)MathF.Round(vector.X) & 0xFF) << 0; uint byte3 = ((uint)MathF.Round(vector.Y) & 0xFF) << 8; @@ -176,4 +184,4 @@ namespace SixLabors.ImageSharp.PixelFormats return byte4 | byte3 | byte2 | byte1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 3ede947139..6be347bccc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -60,20 +60,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - var scaled = new Vector2(vector.X, vector.Y) * 2F; + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; scaled -= Vector2.One; this.PackedValue = Pack(scaled); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -111,11 +111,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -146,7 +154,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { const float MaxVal = 0x7FFF; @@ -156,18 +164,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index 453b1c1b7c..59433f17e2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float MaxVal = 0x7FFF; @@ -118,11 +118,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -148,18 +156,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"NormalizedShort4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -169,7 +177,7 @@ namespace SixLabors.ImageSharp.PixelFormats private static ulong Pack(ref Vector4 vector) { vector *= Max; - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); // Round rather than truncate. ulong word4 = ((ulong)MathF.Round(vector.X) & 0xFFFF) << 0x00; @@ -180,4 +188,4 @@ namespace SixLabors.ImageSharp.PixelFormats return word4 | word3 | word2 | word1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index 0411f8f3e9..60c4010039 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -99,11 +99,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -134,17 +142,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; + public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; /// - public override bool Equals(object obj) => obj is Rg32 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rg32 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -152,7 +160,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(Vector2 vector) @@ -161,4 +169,4 @@ namespace SixLabors.ImageSharp.PixelFormats return (uint)(((int)Math.Round(vector.X) & 0xFFFF) | (((int)Math.Round(vector.Y) & 0xFFFF) << 16)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 469dbbad4a..6e4839fed7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -172,6 +172,25 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = rgb; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); @@ -213,18 +232,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgb24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Rgb24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); /// - public override string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; /// /// Packs a into a color. @@ -235,11 +254,11 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; this.B = (byte)vector.Z; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index f59036ec7d..dff8fe83fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.R = (ushort)MathF.Round(vector.X); this.G = (ushort)MathF.Round(vector.Y); this.B = (ushort)MathF.Round(vector.Z); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); this.R = rgb; @@ -142,13 +142,32 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { this.R = source.PackedValue; this.G = source.PackedValue; this.B = source.PackedValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -182,17 +201,17 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgb48(Rgb48 source) => this = source; /// - public override bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); + public override readonly bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// - public override string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index 38f61d56c1..7ca47f8387 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (this.PackedValue >> 0) & 0x03FF, @@ -105,11 +105,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -135,14 +143,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; + public readonly bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Rgba1010102({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -150,12 +158,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Multiplier; return (uint)( (((int)Math.Round(vector.X) & 0x03FF) << 0) @@ -164,4 +172,4 @@ namespace SixLabors.ImageSharp.PixelFormats | (((int)Math.Round(vector.W) & 0x03) << 30)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs deleted file mode 100644 index f9cc3256cd..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides standardized definitions for named colors. - /// - public partial struct Rgba32 - { - /// - /// Represents a matching the W3C definition that has an hex value of #F0F8FF. - /// - public static readonly Rgba32 AliceBlue = Color.AliceBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAEBD7. - /// - public static readonly Rgba32 AntiqueWhite = Color.AntiqueWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Rgba32 Aqua = Color.Aqua; - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFFD4. - /// - public static readonly Rgba32 Aquamarine = Color.Aquamarine; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFFF. - /// - public static readonly Rgba32 Azure = Color.Azure; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5DC. - /// - public static readonly Rgba32 Beige = Color.Beige; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4C4. - /// - public static readonly Rgba32 Bisque = Color.Bisque; - - /// - /// Represents a matching the W3C definition that has an hex value of #000000. - /// - public static readonly Rgba32 Black = Color.Black; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEBCD. - /// - public static readonly Rgba32 BlanchedAlmond = Color.BlanchedAlmond; - - /// - /// Represents a matching the W3C definition that has an hex value of #0000FF. - /// - public static readonly Rgba32 Blue = Color.Blue; - - /// - /// Represents a matching the W3C definition that has an hex value of #8A2BE2. - /// - public static readonly Rgba32 BlueViolet = Color.BlueViolet; - - /// - /// Represents a matching the W3C definition that has an hex value of #A52A2A. - /// - public static readonly Rgba32 Brown = Color.Brown; - - /// - /// Represents a matching the W3C definition that has an hex value of #DEB887. - /// - public static readonly Rgba32 BurlyWood = Color.BurlyWood; - - /// - /// Represents a matching the W3C definition that has an hex value of #5F9EA0. - /// - public static readonly Rgba32 CadetBlue = Color.CadetBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFF00. - /// - public static readonly Rgba32 Chartreuse = Color.Chartreuse; - - /// - /// Represents a matching the W3C definition that has an hex value of #D2691E. - /// - public static readonly Rgba32 Chocolate = Color.Chocolate; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF7F50. - /// - public static readonly Rgba32 Coral = Color.Coral; - - /// - /// Represents a matching the W3C definition that has an hex value of #6495ED. - /// - public static readonly Rgba32 CornflowerBlue = Color.CornflowerBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF8DC. - /// - public static readonly Rgba32 Cornsilk = Color.Cornsilk; - - /// - /// Represents a matching the W3C definition that has an hex value of #DC143C. - /// - public static readonly Rgba32 Crimson = Color.Crimson; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Rgba32 Cyan = Color.Cyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #00008B. - /// - public static readonly Rgba32 DarkBlue = Color.DarkBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #008B8B. - /// - public static readonly Rgba32 DarkCyan = Color.DarkCyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #B8860B. - /// - public static readonly Rgba32 DarkGoldenrod = Color.DarkGoldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #A9A9A9. - /// - public static readonly Rgba32 DarkGray = Color.DarkGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #006400. - /// - public static readonly Rgba32 DarkGreen = Color.DarkGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #BDB76B. - /// - public static readonly Rgba32 DarkKhaki = Color.DarkKhaki; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B008B. - /// - public static readonly Rgba32 DarkMagenta = Color.DarkMagenta; - - /// - /// Represents a matching the W3C definition that has an hex value of #556B2F. - /// - public static readonly Rgba32 DarkOliveGreen = Color.DarkOliveGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF8C00. - /// - public static readonly Rgba32 DarkOrange = Color.DarkOrange; - - /// - /// Represents a matching the W3C definition that has an hex value of #9932CC. - /// - public static readonly Rgba32 DarkOrchid = Color.DarkOrchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B0000. - /// - public static readonly Rgba32 DarkRed = Color.DarkRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #E9967A. - /// - public static readonly Rgba32 DarkSalmon = Color.DarkSalmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #8FBC8B. - /// - public static readonly Rgba32 DarkSeaGreen = Color.DarkSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #483D8B. - /// - public static readonly Rgba32 DarkSlateBlue = Color.DarkSlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #2F4F4F. - /// - public static readonly Rgba32 DarkSlateGray = Color.DarkSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #00CED1. - /// - public static readonly Rgba32 DarkTurquoise = Color.DarkTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #9400D3. - /// - public static readonly Rgba32 DarkViolet = Color.DarkViolet; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF1493. - /// - public static readonly Rgba32 DeepPink = Color.DeepPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #00BFFF. - /// - public static readonly Rgba32 DeepSkyBlue = Color.DeepSkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #696969. - /// - public static readonly Rgba32 DimGray = Color.DimGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #1E90FF. - /// - public static readonly Rgba32 DodgerBlue = Color.DodgerBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #B22222. - /// - public static readonly Rgba32 Firebrick = Color.Firebrick; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAF0. - /// - public static readonly Rgba32 FloralWhite = Color.FloralWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #228B22. - /// - public static readonly Rgba32 ForestGreen = Color.ForestGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Rgba32 Fuchsia = Color.Fuchsia; - - /// - /// Represents a matching the W3C definition that has an hex value of #DCDCDC. - /// - public static readonly Rgba32 Gainsboro = Color.Gainsboro; - - /// - /// Represents a matching the W3C definition that has an hex value of #F8F8FF. - /// - public static readonly Rgba32 GhostWhite = Color.GhostWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFD700. - /// - public static readonly Rgba32 Gold = Color.Gold; - - /// - /// Represents a matching the W3C definition that has an hex value of #DAA520. - /// - public static readonly Rgba32 Goldenrod = Color.Goldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #808080. - /// - public static readonly Rgba32 Gray = Color.Gray; - - /// - /// Represents a matching the W3C definition that has an hex value of #008000. - /// - public static readonly Rgba32 Green = Color.Green; - - /// - /// Represents a matching the W3C definition that has an hex value of #ADFF2F. - /// - public static readonly Rgba32 GreenYellow = Color.GreenYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFF0. - /// - public static readonly Rgba32 Honeydew = Color.Honeydew; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF69B4. - /// - public static readonly Rgba32 HotPink = Color.HotPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #CD5C5C. - /// - public static readonly Rgba32 IndianRed = Color.IndianRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #4B0082. - /// - public static readonly Rgba32 Indigo = Color.Indigo; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFF0. - /// - public static readonly Rgba32 Ivory = Color.Ivory; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0E68C. - /// - public static readonly Rgba32 Khaki = Color.Khaki; - - /// - /// Represents a matching the W3C definition that has an hex value of #E6E6FA. - /// - public static readonly Rgba32 Lavender = Color.Lavender; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF0F5. - /// - public static readonly Rgba32 LavenderBlush = Color.LavenderBlush; - - /// - /// Represents a matching the W3C definition that has an hex value of #7CFC00. - /// - public static readonly Rgba32 LawnGreen = Color.LawnGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFACD. - /// - public static readonly Rgba32 LemonChiffon = Color.LemonChiffon; - - /// - /// Represents a matching the W3C definition that has an hex value of #ADD8E6. - /// - public static readonly Rgba32 LightBlue = Color.LightBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #F08080. - /// - public static readonly Rgba32 LightCoral = Color.LightCoral; - - /// - /// Represents a matching the W3C definition that has an hex value of #E0FFFF. - /// - public static readonly Rgba32 LightCyan = Color.LightCyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAFAD2. - /// - public static readonly Rgba32 LightGoldenrodYellow = Color.LightGoldenrodYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #D3D3D3. - /// - public static readonly Rgba32 LightGray = Color.LightGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #90EE90. - /// - public static readonly Rgba32 LightGreen = Color.LightGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFB6C1. - /// - public static readonly Rgba32 LightPink = Color.LightPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA07A. - /// - public static readonly Rgba32 LightSalmon = Color.LightSalmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #20B2AA. - /// - public static readonly Rgba32 LightSeaGreen = Color.LightSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEFA. - /// - public static readonly Rgba32 LightSkyBlue = Color.LightSkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #778899. - /// - public static readonly Rgba32 LightSlateGray = Color.LightSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0C4DE. - /// - public static readonly Rgba32 LightSteelBlue = Color.LightSteelBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFE0. - /// - public static readonly Rgba32 LightYellow = Color.LightYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF00. - /// - public static readonly Rgba32 Lime = Color.Lime; - - /// - /// Represents a matching the W3C definition that has an hex value of #32CD32. - /// - public static readonly Rgba32 LimeGreen = Color.LimeGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAF0E6. - /// - public static readonly Rgba32 Linen = Color.Linen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Rgba32 Magenta = Color.Magenta; - - /// - /// Represents a matching the W3C definition that has an hex value of #800000. - /// - public static readonly Rgba32 Maroon = Color.Maroon; - - /// - /// Represents a matching the W3C definition that has an hex value of #66CDAA. - /// - public static readonly Rgba32 MediumAquamarine = Color.MediumAquamarine; - - /// - /// Represents a matching the W3C definition that has an hex value of #0000CD. - /// - public static readonly Rgba32 MediumBlue = Color.MediumBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #BA55D3. - /// - public static readonly Rgba32 MediumOrchid = Color.MediumOrchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #9370DB. - /// - public static readonly Rgba32 MediumPurple = Color.MediumPurple; - - /// - /// Represents a matching the W3C definition that has an hex value of #3CB371. - /// - public static readonly Rgba32 MediumSeaGreen = Color.MediumSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #7B68EE. - /// - public static readonly Rgba32 MediumSlateBlue = Color.MediumSlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FA9A. - /// - public static readonly Rgba32 MediumSpringGreen = Color.MediumSpringGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #48D1CC. - /// - public static readonly Rgba32 MediumTurquoise = Color.MediumTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #C71585. - /// - public static readonly Rgba32 MediumVioletRed = Color.MediumVioletRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #191970. - /// - public static readonly Rgba32 MidnightBlue = Color.MidnightBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5FFFA. - /// - public static readonly Rgba32 MintCream = Color.MintCream; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4E1. - /// - public static readonly Rgba32 MistyRose = Color.MistyRose; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4B5. - /// - public static readonly Rgba32 Moccasin = Color.Moccasin; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDEAD. - /// - public static readonly Rgba32 NavajoWhite = Color.NavajoWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #000080. - /// - public static readonly Rgba32 Navy = Color.Navy; - - /// - /// Represents a matching the W3C definition that has an hex value of #FDF5E6. - /// - public static readonly Rgba32 OldLace = Color.OldLace; - - /// - /// Represents a matching the W3C definition that has an hex value of #808000. - /// - public static readonly Rgba32 Olive = Color.Olive; - - /// - /// Represents a matching the W3C definition that has an hex value of #6B8E23. - /// - public static readonly Rgba32 OliveDrab = Color.OliveDrab; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA500. - /// - public static readonly Rgba32 Orange = Color.Orange; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF4500. - /// - public static readonly Rgba32 OrangeRed = Color.OrangeRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #DA70D6. - /// - public static readonly Rgba32 Orchid = Color.Orchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #EEE8AA. - /// - public static readonly Rgba32 PaleGoldenrod = Color.PaleGoldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #98FB98. - /// - public static readonly Rgba32 PaleGreen = Color.PaleGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #AFEEEE. - /// - public static readonly Rgba32 PaleTurquoise = Color.PaleTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #DB7093. - /// - public static readonly Rgba32 PaleVioletRed = Color.PaleVioletRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEFD5. - /// - public static readonly Rgba32 PapayaWhip = Color.PapayaWhip; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDAB9. - /// - public static readonly Rgba32 PeachPuff = Color.PeachPuff; - - /// - /// Represents a matching the W3C definition that has an hex value of #CD853F. - /// - public static readonly Rgba32 Peru = Color.Peru; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFC0CB. - /// - public static readonly Rgba32 Pink = Color.Pink; - - /// - /// Represents a matching the W3C definition that has an hex value of #DDA0DD. - /// - public static readonly Rgba32 Plum = Color.Plum; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0E0E6. - /// - public static readonly Rgba32 PowderBlue = Color.PowderBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #800080. - /// - public static readonly Rgba32 Purple = Color.Purple; - - /// - /// Represents a matching the W3C definition that has an hex value of #663399. - /// - public static readonly Rgba32 RebeccaPurple = Color.RebeccaPurple; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF0000. - /// - public static readonly Rgba32 Red = Color.Red; - - /// - /// Represents a matching the W3C definition that has an hex value of #BC8F8F. - /// - public static readonly Rgba32 RosyBrown = Color.RosyBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #4169E1. - /// - public static readonly Rgba32 RoyalBlue = Color.RoyalBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B4513. - /// - public static readonly Rgba32 SaddleBrown = Color.SaddleBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #FA8072. - /// - public static readonly Rgba32 Salmon = Color.Salmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #F4A460. - /// - public static readonly Rgba32 SandyBrown = Color.SandyBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #2E8B57. - /// - public static readonly Rgba32 SeaGreen = Color.SeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF5EE. - /// - public static readonly Rgba32 SeaShell = Color.SeaShell; - - /// - /// Represents a matching the W3C definition that has an hex value of #A0522D. - /// - public static readonly Rgba32 Sienna = Color.Sienna; - - /// - /// Represents a matching the W3C definition that has an hex value of #C0C0C0. - /// - public static readonly Rgba32 Silver = Color.Silver; - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEEB. - /// - public static readonly Rgba32 SkyBlue = Color.SkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #6A5ACD. - /// - public static readonly Rgba32 SlateBlue = Color.SlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #708090. - /// - public static readonly Rgba32 SlateGray = Color.SlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAFA. - /// - public static readonly Rgba32 Snow = Color.Snow; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF7F. - /// - public static readonly Rgba32 SpringGreen = Color.SpringGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #4682B4. - /// - public static readonly Rgba32 SteelBlue = Color.SteelBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #D2B48C. - /// - public static readonly Rgba32 Tan = Color.Tan; - - /// - /// Represents a matching the W3C definition that has an hex value of #008080. - /// - public static readonly Rgba32 Teal = Color.Teal; - - /// - /// Represents a matching the W3C definition that has an hex value of #D8BFD8. - /// - public static readonly Rgba32 Thistle = Color.Thistle; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF6347. - /// - public static readonly Rgba32 Tomato = Color.Tomato; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Rgba32 Transparent = Color.Transparent; - - /// - /// Represents a matching the W3C definition that has an hex value of #40E0D0. - /// - public static readonly Rgba32 Turquoise = Color.Turquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #EE82EE. - /// - public static readonly Rgba32 Violet = Color.Violet; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5DEB3. - /// - public static readonly Rgba32 Wheat = Color.Wheat; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Rgba32 White = Color.White; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5F5. - /// - public static readonly Rgba32 WhiteSmoke = Color.WhiteSmoke; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFF00. - /// - public static readonly Rgba32 Yellow = Color.Yellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #9ACD32. - /// - public static readonly Rgba32 YellowGreen = Color.YellowGreen; - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs index da02f374e1..0b0e9b1c18 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -20,36 +20,36 @@ namespace SixLabors.ImageSharp.PixelFormats internal partial class PixelOperations : PixelOperations { /// - internal override void ToVector4( + public override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors, + Span destinationVectors, PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); - destVectors = destVectors.Slice(0, sourcePixels.Length); - SimdUtils.BulkConvertByteToNormalizedFloat( + destinationVectors = destinationVectors.Slice(0, sourcePixels.Length); + SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(sourcePixels), - MemoryMarshal.Cast(destVectors)); - Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); + MemoryMarshal.Cast(destinationVectors)); + Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); } /// - internal override void FromVector4Destructive( + public override void FromVector4Destructive( Configuration configuration, Span sourceVectors, - Span destPixels, + Span destinationPixels, PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); - destPixels = destPixels.Slice(0, sourceVectors.Length); + destinationPixels = destinationPixels.Slice(0, sourceVectors.Length); Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + SimdUtils.NormalizedFloatToByteSaturate( MemoryMarshal.Cast(sourceVectors), - MemoryMarshal.Cast(destPixels)); + MemoryMarshal.Cast(destinationPixels)); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index ba259ca8ed..43ec095a12 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Rgba { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Rgb24 Rgb { [MethodImpl(InliningOptions.ShortMethod)] - get => new Rgb24(this.R, this.G, this.B); + readonly get => new Rgb24(this.R, this.G, this.B); [MethodImpl(InliningOptions.ShortMethod)] set @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Bgr24 Bgr { [MethodImpl(InliningOptions.ShortMethod)] - get => new Bgr24(this.R, this.G, this.B); + readonly get => new Bgr24(this.R, this.G, this.B); [MethodImpl(InliningOptions.ShortMethod)] set @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint PackedValue { [MethodImpl(InliningOptions.ShortMethod)] - get => this.Rgba; + readonly get => this.Rgba; [MethodImpl(InliningOptions.ShortMethod)] set => this.Rgba = value; @@ -230,7 +230,8 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); /// - /// Creates a new instance of the struct. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged @@ -239,23 +240,54 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static Rgba32 FromHex(string hex) + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgba32 ParseHex(string hex) { - Guard.NotNullOrWhiteSpace(hex, nameof(hex)); + Guard.NotNull(hex, nameof(hex)); + + if (!TryParseHex(hex, out Rgba32 rgba)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + return rgba; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Rgba32 result) + { + result = default; + if (string.IsNullOrWhiteSpace(hex)) + { + return false; + } hex = ToRgbaHex(hex); if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + return false; } packedValue = BinaryPrimitives.ReverseEndianness(packedValue); - return Unsafe.As(ref packedValue); + result = Unsafe.As(ref packedValue); + return true; } /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -263,7 +295,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -271,7 +303,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -307,7 +339,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -317,7 +349,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); this.R = rgb; @@ -326,6 +358,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = byte.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -369,25 +422,25 @@ namespace SixLabors.ImageSharp.PixelFormats /// Converts the value of this instance to a hexadecimal string. /// /// A hexadecimal string representation of the value. - public string ToHex() + public readonly string ToHex() { uint hexOrder = (uint)(this.A << 0 | this.B << 8 | this.G << 16 | this.R << 24); return hexOrder.ToString("X8"); } /// - public override bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); + public override readonly bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); + public readonly bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); /// - public override string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; + public override readonly string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.Rgba.GetHashCode(); + public override readonly int GetHashCode() => this.Rgba.GetHashCode(); /// /// Packs a into a color returning a new instance as a result. @@ -399,7 +452,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); return new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); } @@ -438,7 +491,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; @@ -483,4 +536,4 @@ namespace SixLabors.ImageSharp.PixelFormats return new string(new[] { r, r, g, g, b, b, a, a }); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 978d9b0156..8e5f8f0938 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public Rgba64(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.R = (ushort)MathF.Round(vector.X); this.G = (ushort)MathF.Round(vector.Y); this.B = (ushort)MathF.Round(vector.Z); @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Rgb48 Rgb { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats public ulong PackedValue { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -203,13 +203,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.R = (ushort)MathF.Round(vector.X); this.G = (ushort)MathF.Round(vector.Y); this.B = (ushort)MathF.Round(vector.Z); @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) + public void FromL8(L8 source) { ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); this.R = rgb; @@ -267,7 +267,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) + public void FromL16(L16 source) { this.R = source.PackedValue; this.G = source.PackedValue; @@ -275,6 +275,27 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ushort.MaxValue; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgb24(Rgb24 source) @@ -322,7 +343,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32 ToRgba32() + public readonly Rgba32 ToRgba32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -336,7 +357,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Bgra32 ToBgra32() + public readonly Bgra32 ToBgra32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -350,7 +371,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Argb32 ToArgb32() + public readonly Argb32 ToArgb32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -364,7 +385,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Rgb24 ToRgb24() + public readonly Rgb24 ToRgb24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -377,7 +398,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Bgr24 ToBgr24() + public readonly Bgr24 ToBgr24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -386,17 +407,17 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); + public override readonly bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; + public override readonly string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs index b331dbd99f..0f98712443 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -21,58 +21,58 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { /// - internal override void FromVector4Destructive( + public override void FromVector4Destructive( Configuration configuration, Span sourceVectors, - Span destinationColors, + Span destinationPixels, PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - MemoryMarshal.Cast(sourceVectors).CopyTo(destinationColors); + MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); } /// - internal override void ToVector4( + public override void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors, + Span destinationVectors, PixelConversionModifiers modifiers) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); - MemoryMarshal.Cast(sourcePixels).CopyTo(destVectors); - Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); + MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); + Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); } - internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref Gray8 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destBaseRef, i); + ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); dp.ConvertFromRgbaScaledVector4(sp); } } - internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref Gray16 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destBaseRef, i); + ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); dp.ConvertFromRgbaScaledVector4(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 10c642300c..8a6f882c35 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -94,10 +94,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static RgbaVector FromHex(string hex) => Color.FromHex(hex).ToPixel(); + public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); this.R = vector.X; this.G = vector.Y; this.B = vector.Z; @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -140,11 +140,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -170,7 +178,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Converts the value of this instance to a hexadecimal string. /// /// A hexadecimal string representation of the value. - public string ToHex() + public readonly string ToHex() { // Hex is RRGGBBAA Vector4 vector = this.ToVector4() * Max; @@ -180,23 +188,23 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(RgbaVector other) => + public readonly bool Equals(RgbaVector other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); /// - public override string ToString() + public override readonly string ToString() { return FormattableString.Invariant($"RgbaVector({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); } /// - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 14987e5829..526e831f85 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -66,20 +66,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - var scaled = new Vector2(vector.X, vector.Y) * 65534F; + Vector2 scaled = new Vector2(vector.X, vector.Y) * 65534F; scaled -= new Vector2(32767F); this.PackedValue = Pack(scaled); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += new Vector2(32767F); @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); + public readonly Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -117,11 +117,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -149,21 +157,21 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() => new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); + public readonly Vector2 ToVector2() => new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); /// - public override bool Equals(object obj) => obj is Short2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Short2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index c52b293476..135aa8d582 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += new Vector4(32767F); @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (short)(this.PackedValue & 0xFFFF), @@ -122,11 +122,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -152,21 +160,21 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Short4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Short4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Short4 other) => this.PackedValue.Equals(other); + public readonly bool Equals(Short4 other) => this.PackedValue.Equals(other); /// /// Gets the hash code for the current instance. /// /// Hash code for the instance. [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Short4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -175,7 +183,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] private static ulong Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); // Clamp the value between min and max values ulong word4 = ((ulong)Math.Round(vector.X) & 0xFFFF) << 0x00; @@ -186,4 +194,4 @@ namespace SixLabors.ImageSharp.PixelFormats return word4 | word3 | word2 | word1; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 7002819928..d1f4a11c72 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,15 +13,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Argb32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -36,14 +36,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -51,13 +51,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToArgb32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToArgb32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -85,15 +85,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Bgr24 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -108,14 +108,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -123,13 +123,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToBgr24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToBgr24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToBgr24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -157,15 +157,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Bgra32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -180,14 +180,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -195,13 +195,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -221,167 +221,311 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToBgra32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToBgra32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToBgra32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } /// - /// Converts all pixels in 'source` span of into a span of -s. + /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations - /// The source of data. - /// The to the destination pixels. - internal virtual void FromGray8(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Gray8 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { - ref Gray8 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceBaseRef, i); ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromGray8(sp); + dp.FromL8(sp); } } /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromGray8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromL8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromGray8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromL8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// Converts all pixels of the 'sourcePixels` span to a span of -s. /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray8 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Gray8 dp = ref Unsafe.Add(ref destBaseRef, i); + ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); dp.FromScaledVector4(sp.ToScaledVector4()); } } /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. /// /// A to configure internal operations /// The to the source pixels. /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToGray8Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToL8Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToL8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < source.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromL16(sp); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.ToGray8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + this.FromL16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// - /// Converts all pixels in 'source` span of into a span of -s. + /// Converts all pixels of the 'sourcePixels` span to a span of -s. /// /// A to configure internal operations - /// The source of data. - /// The to the destination pixels. - internal virtual void FromGray16(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The span of source pixels + /// The destination span of data. + public virtual void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } + + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToL16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToL16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Gray16 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref La16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { - ref Gray16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceBaseRef, i); ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromGray16(sp); + dp.FromLa16(sp); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromLa16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } + + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref La16 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); } } /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. /// /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToLa16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToLa16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < source.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromLa32(sp); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromGray16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromLa32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromGray16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromLa32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// Converts all pixels of the 'sourcePixels` span to a span of -s. /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Gray16 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref La32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Gray16 dp = ref Unsafe.Add(ref destBaseRef, i); + ref La32 dp = ref Unsafe.Add(ref destBaseRef, i); dp.FromScaledVector4(sp.ToScaledVector4()); } } /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. /// /// A to configure internal operations /// The to the source pixels. /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToGray16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToLa32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { - this.ToGray16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + this.ToLa32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgb24 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -396,14 +540,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -411,13 +555,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -437,7 +581,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToRgb24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToRgb24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgb24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -445,15 +589,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgba32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -468,14 +612,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -483,13 +627,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -509,7 +653,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToRgba32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToRgba32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgba32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -517,15 +661,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgb48 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -540,14 +684,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -555,13 +699,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -581,7 +725,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToRgb48Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToRgb48Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgb48(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -589,15 +733,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Rgba64 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -612,14 +756,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -627,13 +771,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -653,7 +797,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToRgba64Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToRgba64Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToRgba64(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } @@ -661,15 +805,15 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destPixels) + /// The to the destination pixels. + public virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref Bgra5551 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -684,14 +828,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); + this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); } /// @@ -699,13 +843,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + /// The destination span of data. + public virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -725,9 +869,9 @@ namespace SixLabors.ImageSharp.PixelFormats /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.ToBgra5551(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index 8603012321..d242739645 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -1,4 +1,4 @@ -<# +<# // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. #> @@ -18,15 +18,15 @@ /// /// Converts all pixels in 'source` span of into a span of -s. /// - /// A to configure internal operations + /// A to configure internal operations. /// The source of data. - /// The to the destination pixels. - internal virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destPixels) + /// The to the destination pixels. + public virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destinationPixels) { - Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); ref <#=pixelType#> sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < source.Length; i++) { @@ -41,14 +41,14 @@ /// A helper for that expects a byte span. /// The layout of the data in 'sourceBytes' must be compatible with layout. /// - /// A to configure internal operations + /// A to configure internal operations. /// The to the source bytes. - /// The to the destination pixels. + /// The to the destination pixels. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + public void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) { - this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destPixels); + this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destinationPixels); } <# @@ -62,13 +62,13 @@ /// /// A to configure internal operations /// The span of source pixels - /// The destination span of data. - internal virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destPixels) + /// The destination span of data. + public virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destinationPixels) { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destPixels); + ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < sourcePixels.Length; i++) { @@ -88,7 +88,7 @@ /// The to the destination bytes. /// The number of pixels to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + public void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { this.To<#=pixelType#>(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast>(destBytes)); } @@ -118,11 +118,17 @@ namespace SixLabors.ImageSharp.PixelFormats GenerateFromMethods("Bgra32"); GenerateToDestFormatMethods("Bgra32"); - GenerateFromMethods("Gray8"); - GenerateToDestFormatMethods("Gray8"); + GenerateFromMethods("L8"); + GenerateToDestFormatMethods("L8"); + + GenerateFromMethods("L16"); + GenerateToDestFormatMethods("L16"); + + GenerateFromMethods("La16"); + GenerateToDestFormatMethods("La16"); - GenerateFromMethods("Gray16"); - GenerateToDestFormatMethods("Gray16"); + GenerateFromMethods("La32"); + GenerateToDestFormatMethods("La32"); GenerateFromMethods("Rgb24"); GenerateToDestFormatMethods("Rgb24"); @@ -140,4 +146,4 @@ namespace SixLabors.ImageSharp.PixelFormats GenerateToDestFormatMethods("Bgra5551"); #> } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index 63db674c8e..17af972a80 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats.PixelBlenders; @@ -9,14 +9,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// Provides access to pixel blenders /// public partial class PixelOperations - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Find an instance of the pixel blender. /// /// the blending and composition to apply /// A . - internal PixelBlender GetPixelBlender(GraphicsOptions options) + public PixelBlender GetPixelBlender(GraphicsOptions options) { return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode); } @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The color blending mode to apply /// The alpha composition mode to apply /// A . - internal virtual PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + public virtual PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) { switch (alphaMode) { @@ -214,4 +214,4 @@ namespace SixLabors.ImageSharp.PixelFormats } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index f557f348a1..1e1047e2b1 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The pixel format. public partial class PixelOperations - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Gets the global instance for the pixel type @@ -32,17 +32,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The to the source vectors. - /// The to the destination colors. + /// The to the destination colors. /// The to apply during the conversion - internal virtual void FromVector4Destructive( + public virtual void FromVector4Destructive( Configuration configuration, Span sourceVectors, - Span destPixels, + Span destinationPixels, PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Utils.Vector4Converters.Default.FromVector4(sourceVectors, destPixels, modifiers); + Utils.Vector4Converters.Default.FromVector4(sourceVectors, destinationPixels, modifiers); } /// @@ -55,26 +55,29 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The to the source vectors. - /// The to the destination colors. - internal void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels) => - this.FromVector4Destructive(configuration, sourceVectors, destPixels, PixelConversionModifiers.None); + /// The to the destination colors. + public void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels) + => this.FromVector4Destructive(configuration, sourceVectors, destinationPixels, PixelConversionModifiers.None); /// /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. /// /// A to configure internal operations /// The to the source colors. - /// The to the destination vectors. + /// The to the destination vectors. /// The to apply during the conversion - internal virtual void ToVector4( + public virtual void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors, + Span destinationVectors, PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); - Utils.Vector4Converters.Default.ToVector4(sourcePixels, destVectors, modifiers); + Utils.Vector4Converters.Default.ToVector4(sourcePixels, destinationVectors, modifiers); } /// @@ -82,18 +85,26 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A to configure internal operations /// The to the source colors. - /// The to the destination vectors. - internal virtual void ToVector4( + /// The to the destination vectors. + public void ToVector4( Configuration configuration, ReadOnlySpan sourcePixels, - Span destVectors) => - this.ToVector4(configuration, sourcePixels, destVectors, PixelConversionModifiers.None); + Span destinationVectors) + => this.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.None); - internal virtual void From( + /// + /// Bulk operation that copies the to in + /// format. + /// + /// The destination pixel type. + /// A to configure internal operations. + /// The to the source pixels. + /// The to the destination pixels. + public virtual void From( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - where TSourcePixel : struct, IPixel + where TSourcePixel : unmanaged, IPixel { const int SliceLength = 1024; int numberOfSlices = sourcePixels.Length / SliceLength; @@ -123,17 +134,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - /// Converts 'sourcePixels.Length' pixels from 'sourcePixels' into 'destinationPixels'. + /// Bulk operation that copies the to in + /// format. /// /// The destination pixel type. /// A to configure internal operations. - /// The to the source pixels. - /// The to the destination pixels. - internal virtual void To( + /// The to the source pixels. + /// The to the destination pixels. + public virtual void To( Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - where TDestinationPixel : struct, IPixel + where TDestinationPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index e67bd9d53e..ac50dd8c44 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ApplyBackwardConversionModifiers(sourceVectors, modifiers); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (modifiers.IsDefined(PixelConversionModifiers.Scale)) { @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeFromVector4Core( ReadOnlySpan sourceVectors, Span destPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeToVector4Core( ReadOnlySpan sourcePixels, Span destVectors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeFromScaledVector4Core( ReadOnlySpan sourceVectors, Span destinationColors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeToScaledVector4Core( ReadOnlySpan sourceColors, Span destinationVectors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 0350c669ab..4ee645c207 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils // 'destVectors' and 'lastQuarterOfDestBuffer' are overlapping buffers, // but we are always reading/writing at different positions: - SimdUtils.BulkConvertByteToNormalizedFloat( + SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(lastQuarterOfDestBuffer), MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils { Span tempSpan = tempBuffer.Memory.Span; - SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + SimdUtils.NormalizedFloatToByteSaturate( MemoryMarshal.Cast(sourceVectors), MemoryMarshal.Cast(tempSpan)); @@ -122,8 +122,8 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils return int.MaxValue; } - return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.IsAvx2CompatibleArchitecture ? 256 : 128; + return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.HasVector8 ? 256 : 128; } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index 447869a7d5..ba676b3b88 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) { - Vector4Utils.Premultiply(vectors); + Vector4Utilities.Premultiply(vectors); } } @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils { if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) { - Vector4Utils.UnPremultiply(vectors); + Vector4Utilities.UnPremultiply(vectors); } if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) diff --git a/src/ImageSharp/Primitives/ColorMatrix.cs b/src/ImageSharp/Primitives/ColorMatrix.cs index 11886c9c2a..09a2d17ae1 100644 --- a/src/ImageSharp/Primitives/ColorMatrix.cs +++ b/src/ImageSharp/Primitives/ColorMatrix.cs @@ -6,7 +6,7 @@ using System; using System.Globalization; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Primitives /// The resulting matrix. public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 + value2.M11; m.M12 = value1.M12 + value2.M12; @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Primitives /// The result of the subtraction. public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 - value2.M11; m.M12 = value1.M12 - value2.M12; @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp.Primitives /// The negated matrix. public static ColorMatrix operator -(ColorMatrix value) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = -value.M11; m.M12 = -value.M12; @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Primitives /// The result of the multiplication. public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); // First row m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Primitives /// The scaled matrix. public static ColorMatrix operator *(ColorMatrix value1, float value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 * value2; m.M12 = value1.M12 * value2; diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs index 96883229c4..a5af3f2f71 100644 --- a/src/ImageSharp/Primitives/Complex64.cs +++ b/src/ImageSharp/Primitives/Complex64.cs @@ -5,7 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a complex number, where the real and imaginary parts are stored as values. diff --git a/src/ImageSharp/Primitives/ComplexVector4.cs b/src/ImageSharp/Primitives/ComplexVector4.cs index b90da65b2d..5287ab23ff 100644 --- a/src/ImageSharp/Primitives/ComplexVector4.cs +++ b/src/ImageSharp/Primitives/ComplexVector4.cs @@ -5,7 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// A vector with 4 values of type . diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 170292e29e..3fda03b77d 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -1,12 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Diagnostics; using System.Runtime.CompilerServices; -using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a dense matrix with arbitrary elements. @@ -98,9 +97,9 @@ namespace SixLabors.ImageSharp.Primitives } /// - /// Gets a Span wrapping the Data. + /// Gets a span wrapping the . /// - internal Span Span => new Span(this.Data); + public Span Span => new Span(this.Data); /// /// Gets or sets the item at the specified position. @@ -137,7 +136,7 @@ namespace SixLabors.ImageSharp.Primitives /// [MethodImpl(InliningOptions.ShortMethod)] #pragma warning disable SA1008 // Opening parenthesis should be spaced correctly - public static implicit operator T[,] (in DenseMatrix data) + public static implicit operator T[,](in DenseMatrix data) #pragma warning restore SA1008 // Opening parenthesis should be spaced correctly { var result = new T[data.Rows, data.Columns]; @@ -154,6 +153,24 @@ namespace SixLabors.ImageSharp.Primitives return result; } + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(DenseMatrix left, DenseMatrix right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(DenseMatrix left, DenseMatrix right) + => !(left == right); + /// /// Transposes the rows and columns of the dense matrix. /// @@ -211,15 +228,32 @@ namespace SixLabors.ImageSharp.Primitives } /// - public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); + public override bool Equals(object obj) + => obj is DenseMatrix other && this.Equals(other); /// + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(DenseMatrix other) => this.Columns == other.Columns && this.Rows == other.Rows && this.Span.SequenceEqual(other.Span); /// - public override int GetHashCode() => this.Data.GetHashCode(); + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + { + HashCode code = default; + + code.Add(this.Columns); + code.Add(this.Rows); + + Span span = this.Span; + for (int i = 0; i < span.Length; i++) + { + code.Add(span[i]); + } + + return code.ToHashCode(); + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Primitives/LongRational.cs b/src/ImageSharp/Primitives/LongRational.cs index b15aa4022f..d0f56917e1 100644 --- a/src/ImageSharp/Primitives/LongRational.cs +++ b/src/ImageSharp/Primitives/LongRational.cs @@ -5,7 +5,7 @@ using System; using System.Globalization; using System.Text; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a number that can be expressed as a fraction. diff --git a/src/ImageSharp/Primitives/Matrix3x2Extensions.cs b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs new file mode 100644 index 0000000000..4ddbcc0173 --- /dev/null +++ b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the struct. + /// + public static class Matrix3x2Extensions + { + /// + /// Creates a translation matrix from the given vector. + /// + /// The translation position. + /// A translation matrix. + public static Matrix3x2 CreateTranslation(PointF position) => Matrix3x2.CreateTranslation(position); + + /// + /// Creates a scale matrix that is offset by a given center point. + /// + /// Value to scale by on the X-axis. + /// Value to scale by on the Y-axis. + /// The center point. + /// A scaling matrix. + public static Matrix3x2 CreateScale(float xScale, float yScale, PointF centerPoint) => Matrix3x2.CreateScale(xScale, yScale, centerPoint); + + /// + /// Creates a scale matrix from the given vector scale. + /// + /// The scale to use. + /// A scaling matrix. + public static Matrix3x2 CreateScale(SizeF scales) => Matrix3x2.CreateScale(scales); + + /// + /// Creates a scale matrix from the given vector scale with an offset from the given center point. + /// + /// The scale to use. + /// The center offset. + /// A scaling matrix. + public static Matrix3x2 CreateScale(SizeF scales, PointF centerPoint) => Matrix3x2.CreateScale(scales, centerPoint); + + /// + /// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center. + /// + /// The uniform scale to use. + /// The center offset. + /// A scaling matrix. + public static Matrix3x2 CreateScale(float scale, PointF centerPoint) => Matrix3x2.CreateScale(scale, centerPoint); + + /// + /// Creates a skew matrix from the given angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// A skew matrix. + public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); + + /// + /// Creates a skew matrix from the given angles in radians and a center point. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The center point. + /// A skew matrix. + public static Matrix3x2 CreateSkew(float radiansX, float radiansY, PointF centerPoint) => Matrix3x2.CreateSkew(radiansX, radiansY, centerPoint); + + /// + /// Creates a skew matrix from the given angles in degrees and a center point. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The center point. + /// A skew matrix. + public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); + + /// + /// Creates a rotation matrix using the given rotation in degrees. + /// + /// The amount of rotation, in degrees. + /// A rotation matrix. + public static Matrix3x2 CreateRotationDegrees(float degrees) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees)); + + /// + /// Creates a rotation matrix using the given rotation in radians and a center point. + /// + /// The amount of rotation, in radians. + /// The center point. + /// A rotation matrix. + public static Matrix3x2 CreateRotation(float radians, PointF centerPoint) => Matrix3x2.CreateRotation(radians, centerPoint); + + /// + /// Creates a rotation matrix using the given rotation in degrees and a center point. + /// + /// The amount of rotation, in degrees. + /// The center point. + /// A rotation matrix. + public static Matrix3x2 CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); + } +} diff --git a/src/ImageSharp/Primitives/Number.cs b/src/ImageSharp/Primitives/Number.cs new file mode 100644 index 0000000000..3d575e866b --- /dev/null +++ b/src/ImageSharp/Primitives/Number.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Represents an integral number. + /// + [StructLayout(LayoutKind.Explicit)] + public struct Number : IEquatable, IComparable + { + [FieldOffset(0)] + private readonly int signedValue; + + [FieldOffset(0)] + private readonly uint unsignedValue; + + [FieldOffset(4)] + private readonly bool isSigned; + + /// + /// Initializes a new instance of the struct. + /// + /// The value of the number. + public Number(int value) + : this() + { + this.signedValue = value; + this.isSigned = true; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The value of the number. + public Number(uint value) + : this() + { + this.unsignedValue = value; + this.isSigned = false; + } + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(int value) => new Number(value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(uint value) => new Number(value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(ushort value) => new Number((uint)value); + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator int(Number number) + { + return number.isSigned + ? number.signedValue + : (int)number.unsignedValue.Clamp(0, int.MaxValue); + } + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator uint(Number number) + { + return number.isSigned + ? (uint)number.signedValue.Clamp(0, int.MaxValue) + : number.unsignedValue; + } + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator ushort(Number number) + { + return number.isSigned + ? (ushort)number.signedValue.Clamp(ushort.MinValue, ushort.MaxValue) + : (ushort)number.unsignedValue.Clamp(ushort.MinValue, ushort.MaxValue); + } + + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(Number left, Number right) => Equals(left, right); + + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(Number left, Number right) => !Equals(left, right); + + /// + /// Determines whether the first is more than the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator >(Number left, Number right) => left.CompareTo(right) == 1; + + /// + /// Determines whether the first is less than the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator <(Number left, Number right) => left.CompareTo(right) == -1; + + /// + /// Determines whether the first is more than or equal to the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator >=(Number left, Number right) => left.CompareTo(right) >= 0; + + /// + /// Determines whether the first is less than or equal to the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator <=(Number left, Number right) => left.CompareTo(right) <= 0; + + /// + public int CompareTo(Number other) + { + return this.isSigned + ? this.signedValue.CompareTo(other.signedValue) + : this.unsignedValue.CompareTo(other.unsignedValue); + } + + /// + public override bool Equals(object obj) => obj is Number other && this.Equals(other); + + /// + public bool Equals(Number other) + { + if (this.isSigned != other.isSigned) + { + return false; + } + + return this.isSigned + ? this.signedValue.Equals(other.signedValue) + : this.unsignedValue.Equals(other.unsignedValue); + } + + /// + public override int GetHashCode() + { + return this.isSigned + ? this.signedValue.GetHashCode() + : this.unsignedValue.GetHashCode(); + } + + /// + public override string ToString() => this.ToString(CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. + /// + /// An object that supplies culture-specific formatting information. + /// The string representation of the value of this instance, which consists of a sequence of digits ranging from 0 to 9, without a sign or leading zeros. + public string ToString(IFormatProvider provider) + { + return this.isSigned + ? this.signedValue.ToString(provider) + : this.unsignedValue.ToString(provider); + } + } +} diff --git a/src/ImageSharp/Primitives/Point.cs b/src/ImageSharp/Primitives/Point.cs new file mode 100644 index 0000000000..96e73766b9 --- /dev/null +++ b/src/ImageSharp/Primitives/Point.cs @@ -0,0 +1,288 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Represents an ordered pair of integer x- and y-coordinates that defines a point in + /// a two-dimensional plane. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Point : IEquatable + { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Point Empty = default; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal and vertical position of the point. + public Point(int value) + : this() + { + this.X = LowInt16(value); + this.Y = HighInt16(value); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public Point(int x, int y) + : this() + { + this.X = x; + this.Y = y; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The size. + public Point(Size size) + { + this.X = size.Width; + this.Y = size.Height; + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public int X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public int Y { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PointF(Point point) => new PointF(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(Point point) => new Vector2(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Size(Point point) => new Size(point.X, point.Y); + + /// + /// Negates the given point by multiplying all values by -1. + /// + /// The source point. + /// The negated point. + public static Point operator -(Point value) => new Point(-value.X, -value.Y); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator +(Point point, Size size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator -(Point point, Size size) => Subtract(point, size); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static Point operator *(int left, Point right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static Point operator *(Point left, int right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static Point operator /(Point left, int right) + => new Point(left.X / right, left.Y / right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Point left, Point right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Point left, Point right) => !left.Equals(right); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Add(Point point, Size size) => new Point(unchecked(point.X + size.Width), unchecked(point.Y + size.Height)); + + /// + /// Translates a by the negative of a given value. + /// + /// The point on the left hand of the operand. + /// The value on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Multiply(Point point, int value) => new Point(unchecked(point.X * value), unchecked(point.Y * value)); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Subtract(Point point, Size size) => new Point(unchecked(point.X - size.Width), unchecked(point.Y - size.Height)); + + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Ceiling(PointF point) => new Point(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y))); + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Round(PointF point) => new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y))); + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Round(Vector2 vector) => new Point(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y))); + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Truncate(PointF point) => new Point(unchecked((int)point.X), unchecked((int)point.Y)); + + /// + /// Transforms a point by a specified 3x2 matrix. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); + + /// + /// Deconstructs this point into two integers. + /// + /// The out value for X. + /// The out value for Y. + public void Deconstruct(out int x, out int y) + { + x = this.X; + y = this.Y; + } + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(int dx, int dy) + { + unchecked + { + this.X += dx; + this.Y += dy; + } + } + + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(Point point) => this.Offset(point.X, point.Y); + + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + + /// + public override string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; + + /// + public override bool Equals(object obj) => obj is Point other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + + private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); + + private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/PointF.cs b/src/ImageSharp/Primitives/PointF.cs new file mode 100644 index 0000000000..e43ad4daf1 --- /dev/null +++ b/src/ImageSharp/Primitives/PointF.cs @@ -0,0 +1,293 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in + /// a two-dimensional plane. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct PointF : IEquatable + { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly PointF Empty = default; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public PointF(float x, float y) + : this() + { + this.X = x; + this.Y = y; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The size. + public PointF(SizeF size) + { + this.X = size.Width; + this.Y = size.Height; + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public float X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public float Y { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The vector. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PointF(Vector2 vector) => new PointF(vector.X, vector.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(PointF point) => new Vector2(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified by truncating each of the coordinates. + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(PointF point) => Point.Truncate(point); + + /// + /// Negates the given point by multiplying all values by -1. + /// + /// The source point. + /// The negated point. + public static PointF operator -(PointF value) => new PointF(-value.X, -value.Y); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator +(PointF point, SizeF size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator -(PointF point, PointF size) => Subtract(point, size); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator +(PointF point, PointF size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator -(PointF point, SizeF size) => Subtract(point, size); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static PointF operator *(float left, PointF right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static PointF operator *(PointF left, float right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static PointF operator /(PointF left, float right) + => new PointF(left.X / right, left.Y / right); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(PointF left, PointF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(PointF left, PointF right) => !left.Equals(right); + + /// + /// Translates a by the given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Add(PointF point, SizeF size) => new PointF(point.X + size.Width, point.Y + size.Height); + + /// + /// Translates a by the given . + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Add(PointF point, PointF pointb) => new PointF(point.X + pointb.X, point.Y + pointb.Y); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Subtract(PointF point, SizeF size) => new PointF(point.X - size.Width, point.Y - size.Height); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Subtract(PointF point, PointF pointb) => new PointF(point.X - pointb.X, point.Y - pointb.Y); + + /// + /// Translates a by the multiplying the X and Y by the given value. + /// + /// The point on the left hand of the operand. + /// The value on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Multiply(PointF point, float right) => new PointF(point.X * right, point.Y * right); + + /// + /// Transforms a point by a specified 3x2 matrix. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); + + /// + /// Deconstructs this point into two floats. + /// + /// The out value for X. + /// The out value for Y. + public void Deconstruct(out float x, out float y) + { + x = this.X; + y = this.Y; + } + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(float dx, float dy) + { + this.X += dx; + this.Y += dy; + } + + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(PointF point) => this.Offset(point.X, point.Y); + + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + + /// + public override string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; + + /// + public override bool Equals(object obj) => obj is PointF && this.Equals((PointF)obj); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/Rational.cs b/src/ImageSharp/Primitives/Rational.cs index f9299bc178..212178a246 100644 --- a/src/ImageSharp/Primitives/Rational.cs +++ b/src/ImageSharp/Primitives/Rational.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a number that can be expressed as a fraction. diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs new file mode 100644 index 0000000000..d391057a9b --- /dev/null +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -0,0 +1,463 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Stores a set of four integers that represent the location and size of a rectangle. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Rectangle : IEquatable + { + /// + /// Represents a that has X, Y, Width, and Height values set to zero. + /// + public static readonly Rectangle Empty = default; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public Rectangle(int x, int y, int width, int height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public Rectangle(Point point, Size size) + { + this.X = point.X; + this.Y = point.Y; + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public int X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public int Y { get; set; } + + /// + /// Gets or sets the width of this . + /// + public int Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public int Height { get; set; } + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Point Location + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new Point(this.X, this.Y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.X = value.X; + this.Y = value.Y; + } + } + + /// + /// Gets or sets the size of this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Size Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new Size(this.Width, this.Height); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.Width = value.Width; + this.Height = value.Height; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Gets the y-coordinate of the top edge of this . + /// + public int Top => this.Y; + + /// + /// Gets the x-coordinate of the right edge of this . + /// + public int Right + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => unchecked(this.X + this.Width); + } + + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public int Bottom + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => unchecked(this.Y + this.Height); + } + + /// + /// Gets the x-coordinate of the left edge of this . + /// + public int Left => this.X; + + /// + /// Creates a with the coordinates of the specified . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator RectangleF(Rectangle rectangle) => new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right); + + /// + /// Creates a new with the specified location and size. + /// The left coordinate of the rectangle. + /// The top coordinate of the rectangle. + /// The right coordinate of the rectangle. + /// The bottom coordinate of the rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + // ReSharper disable once InconsistentNaming + public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); + + /// + /// Returns the center point of the given . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Center(Rectangle rectangle) => new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + + /// + /// Creates a rectangle that represents the intersection between and + /// . If there is no intersection, an empty rectangle is returned. + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Intersect(Rectangle a, Rectangle b) + { + int x1 = Math.Max(a.X, b.X); + int x2 = Math.Min(a.Right, b.Right); + int y1 = Math.Max(a.Y, b.Y); + int y2 = Math.Min(a.Bottom, b.Bottom); + + if (x2 >= x1 && y2 >= y1) + { + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + return Empty; + } + + /// + /// Creates a that is inflated by the specified amount. + /// + /// The rectangle. + /// The amount to inflate the width by. + /// The amount to inflate the height by. + /// A new . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Inflate(Rectangle rectangle, int x, int y) + { + Rectangle r = rectangle; + r.Inflate(x, y); + return r; + } + + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Ceiling(RectangleF rectangle) + { + unchecked + { + return new Rectangle( + (int)MathF.Ceiling(rectangle.X), + (int)MathF.Ceiling(rectangle.Y), + (int)MathF.Ceiling(rectangle.Width), + (int)MathF.Ceiling(rectangle.Height)); + } + } + + /// + /// Transforms a rectangle by the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed rectangle. + public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) + { + PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); + PointF topLeft = Point.Transform(rectangle.Location, matrix); + return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + } + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Truncate(RectangleF rectangle) + { + unchecked + { + return new Rectangle( + (int)rectangle.X, + (int)rectangle.Y, + (int)rectangle.Width, + (int)rectangle.Height); + } + } + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Round(RectangleF rectangle) + { + unchecked + { + return new Rectangle( + (int)MathF.Round(rectangle.X), + (int)MathF.Round(rectangle.Y), + (int)MathF.Round(rectangle.Width), + (int)MathF.Round(rectangle.Height)); + } + } + + /// + /// Creates a rectangle that represents the union between and . + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Union(Rectangle a, Rectangle b) + { + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.Right, b.Right); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Bottom, b.Bottom); + + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + /// + /// Deconstructs this rectangle into four integers. + /// + /// The out value for X. + /// The out value for Y. + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out int x, out int y, out int width, out int height) + { + x = this.X; + y = this.Y; + width = this.Width; + height = this.Height; + } + + /// + /// Creates a Rectangle that represents the intersection between this Rectangle and the . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Intersect(Rectangle rectangle) + { + Rectangle result = Intersect(rectangle, this); + + this.X = result.X; + this.Y = result.Y; + this.Width = result.Width; + this.Height = result.Height; + } + + /// + /// Inflates this by the specified amount. + /// + /// The width. + /// The height. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(int width, int height) + { + unchecked + { + this.X -= width; + this.Y -= height; + + this.Width += 2 * width; + this.Height += 2 * height; + } + } + + /// + /// Inflates this by the specified amount. + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(Size size) => this.Inflate(size.Width, size.Height); + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + + /// + /// Determines if the specified point is contained within the rectangular region defined by this . + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Point point) => this.Contains(point.X, point.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained + /// within the rectangular region represented by this . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Rectangle rectangle) => + (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && + (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); + + /// + /// Determines if the specfied intersects the rectangular region defined by + /// this . + /// + /// The other Rectange. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(Rectangle rectangle) => + (rectangle.X < this.Right) && (this.X < rectangle.Right) && + (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(Point point) => this.Offset(point.X, point.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(int dx, int dy) + { + unchecked + { + this.X += dx; + this.Y += dy; + } + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.X, this.Y, this.Width, this.Height); + } + + /// + public override string ToString() + { + return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) => obj is Rectangle other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rectangle other) => + this.X.Equals(other.X) && + this.Y.Equals(other.Y) && + this.Width.Equals(other.Width) && + this.Height.Equals(other.Height); + } +} diff --git a/src/ImageSharp/Primitives/RectangleF.cs b/src/ImageSharp/Primitives/RectangleF.cs new file mode 100644 index 0000000000..354daa4463 --- /dev/null +++ b/src/ImageSharp/Primitives/RectangleF.cs @@ -0,0 +1,396 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Stores a set of four single precision floating points that represent the location and size of a rectangle. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct RectangleF : IEquatable + { + /// + /// Represents a that has X, Y, Width, and Height values set to zero. + /// + public static readonly RectangleF Empty = default; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public RectangleF(float x, float y, float width, float height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public RectangleF(PointF point, SizeF size) + { + this.X = point.X; + this.Y = point.Y; + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public float X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public float Y { get; set; } + + /// + /// Gets or sets the width of this . + /// + public float Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public float Height { get; set; } + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public PointF Location + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new PointF(this.X, this.Y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.X = value.X; + this.Y = value.Y; + } + } + + /// + /// Gets or sets the size of this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public SizeF Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new SizeF(this.Width, this.Height); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.Width = value.Width; + this.Height = value.Height; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); + + /// + /// Gets the y-coordinate of the top edge of this . + /// + public float Top => this.Y; + + /// + /// Gets the x-coordinate of the right edge of this . + /// + public float Right + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.X + this.Width; + } + + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public float Bottom + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.Y + this.Height; + } + + /// + /// Gets the x-coordinate of the left edge of this . + /// + public float Left => this.X; + + /// + /// Creates a with the coordinates of the specified by truncating each coordinate. + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right); + + /// + /// Creates a new with the specified location and size. + /// The left coordinate of the rectangle. + /// The top coordinate of the rectangle. + /// The right coordinate of the rectangle. + /// The bottom coordinate of the rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + // ReSharper disable once InconsistentNaming + public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new RectangleF(left, top, right - left, bottom - top); + + /// + /// Returns the center point of the given . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Center(RectangleF rectangle) => new PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + + /// + /// Creates a rectangle that represents the intersection between and + /// . If there is no intersection, an empty rectangle is returned. + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Intersect(RectangleF a, RectangleF b) + { + float x1 = MathF.Max(a.X, b.X); + float x2 = MathF.Min(a.Right, b.Right); + float y1 = MathF.Max(a.Y, b.Y); + float y2 = MathF.Min(a.Bottom, b.Bottom); + + if (x2 >= x1 && y2 >= y1) + { + return new RectangleF(x1, y1, x2 - x1, y2 - y1); + } + + return Empty; + } + + /// + /// Creates a that is inflated by the specified amount. + /// + /// The rectangle. + /// The amount to inflate the width by. + /// The amount to inflate the height by. + /// A new . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Inflate(RectangleF rectangle, float x, float y) + { + RectangleF r = rectangle; + r.Inflate(x, y); + return r; + } + + /// + /// Transforms a rectangle by the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed . + public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) + { + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + PointF topLeft = PointF.Transform(rectangle.Location, matrix); + return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + } + + /// + /// Creates a rectangle that represents the union between and . + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Union(RectangleF a, RectangleF b) + { + float x1 = MathF.Min(a.X, b.X); + float x2 = MathF.Max(a.Right, b.Right); + float y1 = MathF.Min(a.Y, b.Y); + float y2 = MathF.Max(a.Bottom, b.Bottom); + + return new RectangleF(x1, y1, x2 - x1, y2 - y1); + } + + /// + /// Deconstructs this rectangle into four floats. + /// + /// The out value for X. + /// The out value for Y. + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out float x, out float y, out float width, out float height) + { + x = this.X; + y = this.Y; + width = this.Width; + height = this.Height; + } + + /// + /// Creates a RectangleF that represents the intersection between this RectangleF and the . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Intersect(RectangleF rectangle) + { + RectangleF result = Intersect(rectangle, this); + + this.X = result.X; + this.Y = result.Y; + this.Width = result.Width; + this.Height = result.Height; + } + + /// + /// Inflates this by the specified amount. + /// + /// The width. + /// The height. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(float width, float height) + { + this.X -= width; + this.Y -= height; + + this.Width += 2 * width; + this.Height += 2 * height; + } + + /// + /// Inflates this by the specified amount. + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height); + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + + /// + /// Determines if the specified point is contained within the rectangular region defined by this . + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(PointF point) => this.Contains(point.X, point.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained + /// within the rectangular region represented by this . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(RectangleF rectangle) => + (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && + (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); + + /// + /// Determines if the specfied intersects the rectangular region defined by + /// this . + /// + /// The other Rectange. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(RectangleF rectangle) => + (rectangle.X < this.Right) && (this.X < rectangle.Right) && + (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(PointF point) => this.Offset(point.X, point.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(float dx, float dy) + { + this.X += dx; + this.Y += dy; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.X, this.Y, this.Width, this.Height); + } + + /// + public override string ToString() + { + return $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) => obj is RectangleF other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(RectangleF other) => + this.X.Equals(other.X) && + this.Y.Equals(other.Y) && + this.Width.Equals(other.Width) && + this.Height.Equals(other.Height); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/SignedRational.cs b/src/ImageSharp/Primitives/SignedRational.cs index 395a24b14e..93a0ffe39f 100644 --- a/src/ImageSharp/Primitives/SignedRational.cs +++ b/src/ImageSharp/Primitives/SignedRational.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a number that can be expressed as a fraction. diff --git a/src/ImageSharp/Primitives/Size.cs b/src/ImageSharp/Primitives/Size.cs new file mode 100644 index 0000000000..effd657a6c --- /dev/null +++ b/src/ImageSharp/Primitives/Size.cs @@ -0,0 +1,296 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Stores an ordered pair of integers, which specify a height and width. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Size : IEquatable + { + /// + /// Represents a that has Width and Height values set to zero. + /// + public static readonly Size Empty = default; + + /// + /// Initializes a new instance of the struct. + /// + /// The width and height of the size. + public Size(int value) + : this() + { + this.Width = value; + this.Height = value; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public Size(int width, int height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The size. + public Size(Size size) + : this() + { + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The point. + public Size(Point point) + { + this.Width = point.X; + this.Height = point.Y; + } + + /// + /// Gets or sets the width of this . + /// + public int Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public int Height { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the dimensions of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height); + + /// + /// Converts the given into a . + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(Size size) => new Point(size.Width, size.Height); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size operator +(Size left, Size right) => Add(left, right); + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size operator -(Size left, Size right) => Subtract(left, right); + + /// + /// Multiplies a by an producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static Size operator *(int left, Size right) => Multiply(right, left); + + /// + /// Multiplies by an producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static Size operator *(Size left, int right) => Multiply(left, right); + + /// + /// Divides by an producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static Size operator /(Size left, int right) => new Size(unchecked(left.Width / right), unchecked(left.Height / right)); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static SizeF operator *(float left, Size right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static SizeF operator *(Size left, float right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static SizeF operator /(Size left, float right) + => new SizeF(left.Width / right, left.Height / right); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Size left, Size right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Size left, Size right) => !left.Equals(right); + + /// + /// Performs vector addition of two objects. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height)); + + /// + /// Contracts a by another . + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height)); + + /// + /// Converts a to a by performing a ceiling operation on all the dimensions. + /// + /// The size. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height))); + + /// + /// Converts a to a by performing a round operation on all the dimensions. + /// + /// The size. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height))); + + /// + /// Transforms a size by the given matrix. + /// + /// The source size. + /// The transformation matrix. + /// A transformed size. + public static SizeF Transform(Size size, Matrix3x2 matrix) + { + var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); + + return new SizeF(v.X, v.Y); + } + + /// + /// Converts a to a by performing a round operation on all the dimensions. + /// + /// The size. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); + + /// + /// Deconstructs this size into two integers. + /// + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out int width, out int height) + { + width = this.Width; + height = this.Height; + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); + + /// + public override string ToString() => $"Size [ Width={this.Width}, Height={this.Height} ]"; + + /// + public override bool Equals(object obj) => obj is Size other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Size other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); + + /// + /// Multiplies by an producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + private static Size Multiply(Size size, int multiplier) => + new Size(unchecked(size.Width * multiplier), unchecked(size.Height * multiplier)); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type SizeF. + private static SizeF Multiply(Size size, float multiplier) => + new SizeF(size.Width * multiplier, size.Height * multiplier); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/SizeF.cs b/src/ImageSharp/Primitives/SizeF.cs new file mode 100644 index 0000000000..7d9bc58146 --- /dev/null +++ b/src/ImageSharp/Primitives/SizeF.cs @@ -0,0 +1,233 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.ComponentModel; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Stores an ordered pair of single precision floating points, which specify a height and width. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct SizeF : IEquatable + { + /// + /// Represents a that has Width and Height values set to zero. + /// + public static readonly SizeF Empty = default; + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public SizeF(float width, float height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The size. + public SizeF(SizeF size) + : this() + { + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The point. + public SizeF(PointF point) + { + this.Width = point.X; + this.Height = point.Y; + } + + /// + /// Gets or sets the width of this . + /// + public float Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public float Height { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(SizeF point) => new Vector2(point.Width, point.Height); + + /// + /// Creates a with the dimensions of the specified by truncating each of the dimensions. + /// + /// The size. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Size(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); + + /// + /// Converts the given into a . + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator PointF(SizeF size) => new PointF(size.Width, size.Height); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF operator +(SizeF left, SizeF right) => Add(left, right); + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static SizeF operator *(float left, SizeF right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static SizeF operator *(SizeF left, float right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static SizeF operator /(SizeF left, float right) + => new SizeF(left.Width / right, left.Height / right); + + /// + /// Compares two objects for equality. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(SizeF left, SizeF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right); + + /// + /// Performs vector addition of two objects. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF Add(SizeF left, SizeF right) => new SizeF(left.Width + right.Width, left.Height + right.Height); + + /// + /// Contracts a by another . + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF Subtract(SizeF left, SizeF right) => new SizeF(left.Width - right.Width, left.Height - right.Height); + + /// + /// Transforms a size by the given matrix. + /// + /// The source size. + /// The transformation matrix. + /// A transformed size. + public static SizeF Transform(SizeF size, Matrix3x2 matrix) + { + var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); + + return new SizeF(v.X, v.Y); + } + + /// + /// Deconstructs this size into two floats. + /// + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out float width, out float height) + { + width = this.Width; + height = this.Height; + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); + + /// + public override string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; + + /// + public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type SizeF. + private static SizeF Multiply(SizeF size, float multiplier) => + new SizeF(size.Width * multiplier, size.Height * multiplier); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/ValueSize.cs b/src/ImageSharp/Primitives/ValueSize.cs index 577e9187a6..be2ccb7251 100644 --- a/src/ImageSharp/Primitives/ValueSize.cs +++ b/src/ImageSharp/Primitives/ValueSize.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Primitives +namespace SixLabors.ImageSharp { /// /// Represents a value in relation to a value on the image. diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs new file mode 100644 index 0000000000..3279d96e3a --- /dev/null +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Binarization; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Extensions to perform AdaptiveThreshold through Mutator. + /// + public static class AdaptiveThresholdExtensions + { + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source) + => source.ApplyProcessor(new AdaptiveThresholdProcessor()); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Threshold limit (0.0-1.0) to consider for binarization. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit)); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower)); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// Threshold limit (0.0-1.0) to consider for binarization. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit)); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// Rectangle region to apply the processor on. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// Threshold limit (0.0-1.0) to consider for binarization. + /// Rectangle region to apply the processor on. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit, Rectangle rectangle) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle); + } +} diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index c3d01241c9..1478d2951b 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -1,11 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Prepends a rotation matrix using the given rotation in degrees at the given origin. @@ -68,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Appends a rotation matrix using the given rotation in degrees at the given origin. @@ -143,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Prepends a centered skew matrix from the give angles in radians. @@ -152,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -181,7 +180,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Appends a centered skew matrix from the give angles in radians. @@ -190,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -248,15 +247,33 @@ namespace SixLabors.ImageSharp.Processing /// Prepends a raw matrix. /// /// The matrix to prepend. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// /// The . - public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) => this.Prepend(_ => matrix); + public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) + { + CheckDegenerate(matrix); + return this.Prepend(_ => matrix); + } /// /// Appends a raw matrix. /// /// The matrix to append. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// /// The . - public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) => this.Append(_ => matrix); + public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) + { + CheckDegenerate(matrix); + return this.Append(_ => matrix); + } /// /// Returns the combined matrix for a given source size. @@ -269,6 +286,11 @@ namespace SixLabors.ImageSharp.Processing /// Returns the combined matrix for a given source rectangle. /// /// The rectangle in the source image. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// /// The . public Matrix3x2 BuildMatrix(Rectangle sourceRectangle) { @@ -285,9 +307,19 @@ namespace SixLabors.ImageSharp.Processing matrix *= factory(size); } + CheckDegenerate(matrix); + return matrix; } + private static void CheckDegenerate(Matrix3x2 matrix) + { + if (TransformUtilities.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + } + } + private AffineTransformBuilder Prepend(Func factory) { this.matrixFactories.Insert(0, factory); @@ -300,4 +332,4 @@ namespace SixLabors.ImageSharp.Processing return this; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/BokehBlurExecutionMode.cs b/src/ImageSharp/Processing/BokehBlurExecutionMode.cs deleted file mode 100644 index bc44dca03c..0000000000 --- a/src/ImageSharp/Processing/BokehBlurExecutionMode.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// An that indicates execution options for the . - /// - public enum BokehBlurExecutionMode - { - /// - /// Indicates that the maximum performance should be prioritized over memory usage. - /// - PreferMaximumPerformance, - - /// - /// Indicates that the memory usage should be prioritized over raw performance. - /// - PreferLowMemoryUsage - } -} diff --git a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs index 328ccdf941..714a45f5f0 100644 --- a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs +++ b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs @@ -1,11 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Advanced; +using System.Collections.Generic; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { @@ -14,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The pixel format internal class DefaultImageProcessorContext : IInternalImageProcessingContext - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly bool mutate; private readonly Image source; @@ -23,10 +21,12 @@ namespace SixLabors.ImageSharp.Processing /// /// Initializes a new instance of the class. /// - /// The image. - /// The mutate. - public DefaultImageProcessorContext(Image source, bool mutate) + /// The configuration which allows altering default behaviour or extending the library. + /// The source image. + /// Whether to mutate the image. + public DefaultImageProcessorContext(Configuration configuration, Image source, bool mutate) { + this.Configuration = configuration; this.mutate = mutate; this.source = source; @@ -38,7 +38,10 @@ namespace SixLabors.ImageSharp.Processing } /// - public MemoryAllocator MemoryAllocator => this.source.GetConfiguration().MemoryAllocator; + public Configuration Configuration { get; } + + /// + public IDictionary Properties { get; } = new Dictionary(); /// public Image GetResultImage() @@ -71,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing // interim clone if the first processor in the pipeline is a cloning processor. if (processor is ICloningImageProcessor cloningImageProcessor) { - using (ICloningImageProcessor pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.source, rectangle)) + using (ICloningImageProcessor pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.Configuration, this.source, rectangle)) { this.destination = pixelProcessor.CloneAndExecute(); return this; @@ -83,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing } // Standard processing pipeline. - using (IImageProcessor specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle)) + using (IImageProcessor specificProcessor = processor.CreatePixelSpecificProcessor(this.Configuration, this.destination, rectangle)) { specificProcessor.Execute(); } diff --git a/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs deleted file mode 100644 index dd1cc1ed24..0000000000 --- a/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extension methods to replace the background color of an - /// using Mutate/Clone. - /// - public static class BackgroundColorExtensions - { - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The color to set as the background. - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => - BackgroundColor(source, GraphicsOptions.Default, color); - - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The color to set as the background. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - Color color, - Rectangle rectangle) => - BackgroundColor(source, GraphicsOptions.Default, color, rectangle); - - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the background. - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.ApplyProcessor(new BackgroundColorProcessor(color, options)); - - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the background. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - Rectangle rectangle) => - source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs new file mode 100644 index 0000000000..659b538fcc --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions to apply binary dithering on an + /// using Mutate/Clone. + /// + public static class BinaryDitherExtensions + { + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The to allow chaining of operations. + public static IImageProcessingContext + BinaryDither(this IImageProcessingContext source, IDither dither) => + BinaryDither(source, dither, Color.White, Color.Black); + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IDither dither, + Color upperColor, + Color lowerColor) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor })); + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IDither dither, + Rectangle rectangle) => + BinaryDither(source, dither, Color.White, Color.Black, rectangle); + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IDither dither, + Color upperColor, + Color lowerColor, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor }), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs new file mode 100644 index 0000000000..d4fe9b562e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Binarization; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extension methods to apply binary thresholding on an + /// using Mutate/Clone. + /// + public static class BinaryThresholdExtensions + { + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Rectangle rectangle) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor, + Rectangle rectangle) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/BinaryDiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/BinaryDiffuseExtensions.cs deleted file mode 100644 index 760102aacf..0000000000 --- a/src/ImageSharp/Processing/Extensions/BinaryDiffuseExtensions.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extension methods to apply binary diffusion on an - /// using Mutate/Clone. - /// - public static class BinaryDiffuseExtensions - { - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold) => - source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold)); - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold), rectangle); - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Color upperColor, - Color lowerColor) => - source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor)); - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Color upperColor, - Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor( - new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor), - rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/BinaryDitherExtensions.cs b/src/ImageSharp/Processing/Extensions/BinaryDitherExtensions.cs deleted file mode 100644 index e8ce252a2d..0000000000 --- a/src/ImageSharp/Processing/Extensions/BinaryDitherExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions to apply binary dithering on an - /// using Mutate/Clone. - /// - public static class BinaryDitherExtensions - { - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The to allow chaining of operations. - public static IImageProcessingContext - BinaryDither(this IImageProcessingContext source, IOrderedDither dither) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither)); - - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IOrderedDither dither, - Color upperColor, - Color lowerColor) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor)); - - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IOrderedDither dither, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither), rectangle); - - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IOrderedDither dither, - Color upperColor, - Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/Extensions/BinaryThresholdExtensions.cs deleted file mode 100644 index 35aa681e33..0000000000 --- a/src/ImageSharp/Processing/Extensions/BinaryThresholdExtensions.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extension methods to apply binary thresholding on an - /// using Mutate/Clone. - /// - public static class BinaryThresholdExtensions - { - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold)); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor)); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs deleted file mode 100644 index c148ccbcb9..0000000000 --- a/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extension methods that allow the application of black and white toning to an - /// using Mutate/Clone. - /// - public static class BlackWhiteExtensions - { - /// - /// Applies black and white toning to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) - => source.ApplyProcessor(new BlackWhiteProcessor()); - - /// - /// Applies black and white toning to the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BlackWhite(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs deleted file mode 100644 index ef20f940af..0000000000 --- a/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds bokeh blurring extensions to the type. - /// - public static class BokehBlurExtensions - { - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new BokehBlurProcessor()); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The execution mode to use when applying the processor. - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, BokehBlurExecutionMode executionMode) - => source.ApplyProcessor(new BokehBlurProcessor(executionMode)); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma)); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// The execution mode to use when applying the processor. - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode)); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The execution mode to use when applying the processor. - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, BokehBlurExecutionMode executionMode) - => source.ApplyProcessor(new BokehBlurProcessor(executionMode), rectangle); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); - - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// The execution mode to use when applying the processor. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode, Rectangle rectangle) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode), rectangle); - } -} diff --git a/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs deleted file mode 100644 index 42dfd425cc..0000000000 --- a/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions methods to apply box blurring to an - /// using Mutate/Clone. - /// - public static class BoxBlurExtensions - { - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new BoxBlurProcessor()); - - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) - => source.ApplyProcessor(new BoxBlurProcessor(radius)); - - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) - => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs deleted file mode 100644 index 8e43f06c5a..0000000000 --- a/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the alteration of the brightness component of an - /// using Mutate/Clone. - /// - public static class BrightnessExtensions - { - /// - /// Alters the brightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new BrightnessProcessor(amount)); - - /// - /// Alters the brightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); - } -} diff --git a/src/ImageSharp/Processing/Extensions/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/Extensions/ColorBlindnessExtensions.cs deleted file mode 100644 index b8d503955e..0000000000 --- a/src/ImageSharp/Processing/Extensions/ColorBlindnessExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that simulate the effects of various color blindness disorders on an - /// using Mutate/Clone. - /// - public static class ColorBlindnessExtensions - { - /// - /// Applies the given colorblindness simulator to the image. - /// - /// The image this method extends. - /// The type of color blindness simulator to apply. - /// The to allow chaining of operations. - public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) - => source.ApplyProcessor(GetProcessor(colorBlindness)); - - /// - /// Applies the given colorblindness simulator to the image. - /// - /// The image this method extends. - /// The type of color blindness simulator to apply. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindnessMode, Rectangle rectangle) - => source.ApplyProcessor(GetProcessor(colorBlindnessMode), rectangle); - - private static IImageProcessor GetProcessor(ColorBlindnessMode colorBlindness) - { - switch (colorBlindness) - { - case ColorBlindnessMode.Achromatomaly: - return new AchromatomalyProcessor(); - case ColorBlindnessMode.Achromatopsia: - return new AchromatopsiaProcessor(); - case ColorBlindnessMode.Deuteranomaly: - return new DeuteranomalyProcessor(); - case ColorBlindnessMode.Deuteranopia: - return new DeuteranopiaProcessor(); - case ColorBlindnessMode.Protanomaly: - return new ProtanomalyProcessor(); - case ColorBlindnessMode.Protanopia: - return new ProtanopiaProcessor(); - case ColorBlindnessMode.Tritanomaly: - return new TritanomalyProcessor(); - default: - return new TritanopiaProcessor(); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/ContrastExtensions.cs b/src/ImageSharp/Processing/Extensions/ContrastExtensions.cs deleted file mode 100644 index bdfd7c98a4..0000000000 --- a/src/ImageSharp/Processing/Extensions/ContrastExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the alteration of the contrast component of an - /// using Mutate/Clone. - /// - public static class ContrastExtensions - { - /// - /// Alters the contrast component of the image. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new ContrastProcessor(amount)); - - /// - /// Alters the contrast component of the image. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs new file mode 100644 index 0000000000..7e0b3df390 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds bokeh blurring extensions to the type. + /// + public static class BokehBlurExtensions + { + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BokehBlurProcessor()); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma)); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); + + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs new file mode 100644 index 0000000000..4534e474a9 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions methods to apply box blurring to an + /// using Mutate/Clone. + /// + public static class BoxBlurExtensions + { + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BoxBlurProcessor()); + + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) + => source.ApplyProcessor(new BoxBlurProcessor(radius)); + + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) + => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs new file mode 100644 index 0000000000..53b2d40b0d --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines edge detection extensions applicable on an using Mutate/Clone. + /// + public static class DetectEdgesExtensions + { + /// + /// Detects any edges within the image. Uses the filter + /// operating in grayscale mode. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => + DetectEdges(source, new SobelProcessor(true)); + + /// + /// Detects any edges within the image. Uses the filter + /// operating in grayscale mode. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) => + DetectEdges(source, rectangle, new SobelProcessor(true)); + + /// + /// Detects any edges within the image. + /// + /// The image this method extends. + /// The filter for detecting edges. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectionOperators filter) => + DetectEdges(source, GetProcessor(filter, true)); + + /// + /// Detects any edges within the image. + /// + /// The image this method extends. + /// The filter for detecting edges. + /// Whether to convert the image to grayscale first. Defaults to true. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectionOperators filter, + bool grayscale) => + DetectEdges(source, GetProcessor(filter, grayscale)); + + /// + /// Detects any edges within the image. + /// + /// The image this method extends. + /// The filter for detecting edges. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// Whether to convert the image to grayscale first. Defaults to true. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectionOperators filter, + Rectangle rectangle, + bool grayscale = true) => + DetectEdges(source, rectangle, GetProcessor(filter, grayscale)); + + /// + /// Detects any edges within the image. + /// + /// The image this method extends. + /// The filter for detecting edges. + /// The to allow chaining of operations. + private static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IImageProcessor filter) + { + return source.ApplyProcessor(filter); + } + + /// + /// Detects any edges within the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The filter for detecting edges. + /// The to allow chaining of operations. + private static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + Rectangle rectangle, + IImageProcessor filter) + { + source.ApplyProcessor(filter, rectangle); + return source; + } + + private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) + { + IImageProcessor processor; + + switch (filter) + { + case EdgeDetectionOperators.Kayyali: + processor = new KayyaliProcessor(grayscale); + break; + + case EdgeDetectionOperators.Kirsch: + processor = new KirschProcessor(grayscale); + break; + + case EdgeDetectionOperators.Laplacian3x3: + processor = new Laplacian3x3Processor(grayscale); + break; + + case EdgeDetectionOperators.Laplacian5x5: + processor = new Laplacian5x5Processor(grayscale); + break; + + case EdgeDetectionOperators.LaplacianOfGaussian: + processor = new LaplacianOfGaussianProcessor(grayscale); + break; + + case EdgeDetectionOperators.Prewitt: + processor = new PrewittProcessor(grayscale); + break; + + case EdgeDetectionOperators.RobertsCross: + processor = new RobertsCrossProcessor(grayscale); + break; + + case EdgeDetectionOperators.Robinson: + processor = new RobinsonProcessor(grayscale); + break; + + case EdgeDetectionOperators.Scharr: + processor = new ScharrProcessor(grayscale); + break; + + default: + processor = new SobelProcessor(grayscale); + break; + } + + return processor; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs new file mode 100644 index 0000000000..9c40d94ed7 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines Gaussian blurring extensions to apply on an + /// using Mutate/Clone. + /// + public static class GaussianBlurExtensions + { + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new GaussianBlurProcessor()); + + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); + + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs new file mode 100644 index 0000000000..007fffb1a2 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines Gaussian sharpening extensions to apply on an + /// using Mutate/Clone. + /// + public static class GaussianSharpenExtensions + { + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => + source.ApplyProcessor(new GaussianSharpenProcessor()); + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) => + source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen( + this IImageProcessingContext source, + float sigma, + Rectangle rectangle) => + source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/CropExtensions.cs b/src/ImageSharp/Processing/Extensions/CropExtensions.cs deleted file mode 100644 index 7ec85169e2..0000000000 --- a/src/ImageSharp/Processing/Extensions/CropExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of cropping operations on an - /// using Mutate/Clone. - /// - public static class CropExtensions - { - /// - /// Crops an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to allow chaining of operations. - public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => - Crop(source, new Rectangle(0, 0, width, height)); - - /// - /// Crops an image to the given rectangle. - /// - /// The image to crop. - /// - /// The structure that specifies the portion of the image object to retain. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => - source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/DetectEdgesExtensions.cs deleted file mode 100644 index 837b26910d..0000000000 --- a/src/ImageSharp/Processing/Extensions/DetectEdgesExtensions.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines edge detection extensions applicable on an using Mutate/Clone. - /// - public static class DetectEdgesExtensions - { - /// - /// Detects any edges within the image. Uses the filter - /// operating in grayscale mode. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => - DetectEdges(source, new SobelProcessor(true)); - - /// - /// Detects any edges within the image. Uses the filter - /// operating in grayscale mode. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) => - DetectEdges(source, rectangle, new SobelProcessor(true)); - - /// - /// Detects any edges within the image. - /// - /// The image this method extends. - /// The filter for detecting edges. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectionOperators filter) => - DetectEdges(source, GetProcessor(filter, true)); - - /// - /// Detects any edges within the image. - /// - /// The image this method extends. - /// The filter for detecting edges. - /// Whether to convert the image to grayscale first. Defaults to true. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectionOperators filter, - bool grayscale) => - DetectEdges(source, GetProcessor(filter, grayscale)); - - /// - /// Detects any edges within the image. - /// - /// The image this method extends. - /// The filter for detecting edges. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// Whether to convert the image to grayscale first. Defaults to true. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectionOperators filter, - Rectangle rectangle, - bool grayscale = true) => - DetectEdges(source, rectangle, GetProcessor(filter, grayscale)); - - /// - /// Detects any edges within the image. - /// - /// The image this method extends. - /// The filter for detecting edges. - /// The to allow chaining of operations. - private static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IImageProcessor filter) - { - return source.ApplyProcessor(filter); - } - - /// - /// Detects any edges within the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The filter for detecting edges. - /// The to allow chaining of operations. - private static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - IImageProcessor filter) - { - source.ApplyProcessor(filter, rectangle); - return source; - } - - private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) - { - IImageProcessor processor; - - switch (filter) - { - case EdgeDetectionOperators.Kayyali: - processor = new KayyaliProcessor(grayscale); - break; - - case EdgeDetectionOperators.Kirsch: - processor = new KirschProcessor(grayscale); - break; - - case EdgeDetectionOperators.Laplacian3x3: - processor = new Laplacian3x3Processor(grayscale); - break; - - case EdgeDetectionOperators.Laplacian5x5: - processor = new Laplacian5x5Processor(grayscale); - break; - - case EdgeDetectionOperators.LaplacianOfGaussian: - processor = new LaplacianOfGaussianProcessor(grayscale); - break; - - case EdgeDetectionOperators.Prewitt: - processor = new PrewittProcessor(grayscale); - break; - - case EdgeDetectionOperators.RobertsCross: - processor = new RobertsCrossProcessor(grayscale); - break; - - case EdgeDetectionOperators.Robinson: - processor = new RobinsonProcessor(grayscale); - break; - - case EdgeDetectionOperators.Scharr: - processor = new ScharrProcessor(grayscale); - break; - - default: - processor = new SobelProcessor(grayscale); - break; - } - - return processor; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs deleted file mode 100644 index f9a1bdde0e..0000000000 --- a/src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Dithering -{ - /// - /// Defines extension methods to apply diffusion to an - /// using Mutate/Clone. - /// - public static class DiffuseExtensions - { - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse(this IImageProcessingContext source) => - Diffuse(source, KnownDiffusers.FloydSteinberg, .5F); - - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse(this IImageProcessingContext source, float threshold) => - Diffuse(source, KnownDiffusers.FloydSteinberg, threshold); - - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold)); - - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Rectangle rectangle) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle); - - /// - /// Dithers the image reducing it to the given palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The palette to select substitute colors from. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - ReadOnlyMemory palette) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette)); - - /// - /// Dithers the image reducing it to the given palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The palette to select substitute colors from. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - ReadOnlyMemory palette, - Rectangle rectangle) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/DitherExtensions.cs deleted file mode 100644 index f83a9e9e81..0000000000 --- a/src/ImageSharp/Processing/Extensions/DitherExtensions.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines dithering extensions to apply on an - /// using Mutate/Clone. - /// - public static class DitherExtensions - { - /// - /// Dithers the image reducing it to a web-safe palette using Bayer4x4 ordered dithering. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Dither(this IImageProcessingContext source) => - Dither(source, KnownDitherers.BayerDither4x4); - - /// - /// Dithers the image reducing it to a web-safe palette using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The to allow chaining of operations. - public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither)); - - /// - /// Dithers the image reducing it to the given palette using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The palette to select substitute colors from. - /// The to allow chaining of operations. - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IOrderedDither dither, - ReadOnlyMemory palette) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette)); - - /// - /// Dithers the image reducing it to a web-safe palette using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IOrderedDither dither, - Rectangle rectangle) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle); - - /// - /// Dithers the image reducing it to the given palette using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The palette to select substitute colors from. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IOrderedDither dither, - ReadOnlyMemory palette, - Rectangle rectangle) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs new file mode 100644 index 0000000000..a04aa0df82 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -0,0 +1,154 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines dithering extensions to apply on an + /// using Mutate/Clone. + /// + public static class DitherExtensions + { + /// + /// Dithers the image reducing it to a web-safe palette using . + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source) => + Dither(source, KnownDitherings.Bayer8x8); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither) => + source.ApplyProcessor(new PaletteDitherProcessor(dither)); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale)); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + ReadOnlyMemory palette) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, palette)); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// The palette to select substitute colors from. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale, + ReadOnlyMemory palette) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette)); + + /// + /// Dithers the image reducing it to a web-safe palette using . + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source, Rectangle rectangle) => + Dither(source, KnownDitherings.Bayer8x8, rectangle); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + ReadOnlyMemory palette, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale, + ReadOnlyMemory palette, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs new file mode 100644 index 0000000000..3c25bb7c40 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Drawing; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of images to the type. + /// + public static class DrawImageExtensions + { + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + float opacity) + { + var options = source.GetGraphicsOptions(); + return source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + options.ColorBlendingMode, + options.AlphaCompositionMode, + opacity)); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The blending mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + PixelColorBlendingMode colorBlending, + float opacity) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + colorBlending, + source.GetGraphicsOptions().AlphaCompositionMode, + opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The color blending mode. + /// The alpha composition mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity) => + source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The options, including the blending type and blending amount. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + GraphicsOptions options) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + options.ColorBlendingMode, + options.AlphaCompositionMode, + options.BlendPercentage)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + float opacity) + { + var options = source.GetGraphicsOptions(); + return source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + options.ColorBlendingMode, + options.AlphaCompositionMode, + opacity)); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The color blending to apply. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + PixelColorBlendingMode colorBlending, + float opacity) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + colorBlending, + source.GetGraphicsOptions().AlphaCompositionMode, + opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The color blending to apply. + /// The alpha composition mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity) => + source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The options containing the blend mode and opacity. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + GraphicsOptions options) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + options.ColorBlendingMode, + options.AlphaCompositionMode, + options.BlendPercentage)); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs new file mode 100644 index 0000000000..5216172819 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Effects; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines oil painting effect extensions applicable on an + /// using Mutate/Clone. + /// + public static class OilPaintExtensions + { + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext OilPaint(this IImageProcessingContext source) => OilPaint(source, 10, 15); + + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext OilPaint(this IImageProcessingContext source, Rectangle rectangle) => + OilPaint(source, 10, 15, rectangle); + + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// The number of neighboring pixels used in calculating each individual pixel value. + /// The to allow chaining of operations. + public static IImageProcessingContext + OilPaint(this IImageProcessingContext source, int levels, int brushSize) => + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize)); + + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// The number of neighboring pixels used in calculating each individual pixel value. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext OilPaint( + this IImageProcessingContext source, + int levels, + int brushSize, + Rectangle rectangle) => + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs new file mode 100644 index 0000000000..b622141b73 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Effects; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extension methods that allow the application of user defined processing delegate to an . + /// + public static class PixelRowDelegateExtensions + { + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) + => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers)); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) + => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers), rectangle); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) + => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers)); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) + => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); + + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs new file mode 100644 index 0000000000..f2a10532d0 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Effects; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines pixelation effect extensions applicable on an + /// using Mutate/Clone. + /// + public static class PixelateExtensions + { + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); + + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => + source.ApplyProcessor(new PixelateProcessor(size)); + + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate( + this IImageProcessingContext source, + int size, + Rectangle rectangle) => + source.ApplyProcessor(new PixelateProcessor(size), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs deleted file mode 100644 index 662e3a6e16..0000000000 --- a/src/ImageSharp/Processing/Extensions/FilterExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of composable filters to an - /// using Mutate/Clone. - /// - public static class FilterExtensions - { - /// - /// Filters an image but the given color matrix - /// - /// The image this method extends. - /// The filter color matrix - /// The to allow chaining of operations. - public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) - => source.ApplyProcessor(new FilterProcessor(matrix)); - - /// - /// Filters an image but the given color matrix - /// - /// The image this method extends. - /// The filter color matrix - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) - => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs new file mode 100644 index 0000000000..788677fc80 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extension methods that allow the application of black and white toning to an + /// using Mutate/Clone. + /// + public static class BlackWhiteExtensions + { + /// + /// Applies black and white toning to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) + => source.ApplyProcessor(new BlackWhiteProcessor()); + + /// + /// Applies black and white toning to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs new file mode 100644 index 0000000000..7bc441297e --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the alteration of the brightness component of an + /// using Mutate/Clone. + /// + public static class BrightnessExtensions + { + /// + /// Alters the brightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new BrightnessProcessor(amount)); + + /// + /// Alters the brightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs new file mode 100644 index 0000000000..e214c5a164 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that simulate the effects of various color blindness disorders on an + /// using Mutate/Clone. + /// + public static class ColorBlindnessExtensions + { + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// The to allow chaining of operations. + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) + => source.ApplyProcessor(GetProcessor(colorBlindness)); + + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindnessMode, Rectangle rectangle) + => source.ApplyProcessor(GetProcessor(colorBlindnessMode), rectangle); + + private static IImageProcessor GetProcessor(ColorBlindnessMode colorBlindness) + { + switch (colorBlindness) + { + case ColorBlindnessMode.Achromatomaly: + return new AchromatomalyProcessor(); + case ColorBlindnessMode.Achromatopsia: + return new AchromatopsiaProcessor(); + case ColorBlindnessMode.Deuteranomaly: + return new DeuteranomalyProcessor(); + case ColorBlindnessMode.Deuteranopia: + return new DeuteranopiaProcessor(); + case ColorBlindnessMode.Protanomaly: + return new ProtanomalyProcessor(); + case ColorBlindnessMode.Protanopia: + return new ProtanopiaProcessor(); + case ColorBlindnessMode.Tritanomaly: + return new TritanomalyProcessor(); + default: + return new TritanopiaProcessor(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs new file mode 100644 index 0000000000..4a3e460b85 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the alteration of the contrast component of an + /// using Mutate/Clone. + /// + public static class ContrastExtensions + { + /// + /// Alters the contrast component of the image. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new ContrastProcessor(amount)); + + /// + /// Alters the contrast component of the image. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs new file mode 100644 index 0000000000..f89540e245 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of composable filters to an + /// using Mutate/Clone. + /// + public static class FilterExtensions + { + /// + /// Filters an image by the given color matrix + /// + /// The image this method extends. + /// The filter color matrix + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) + => source.ApplyProcessor(new FilterProcessor(matrix)); + + /// + /// Filters an image by the given color matrix + /// + /// The image this method extends. + /// The filter color matrix + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) + => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs new file mode 100644 index 0000000000..4125de8321 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of grayscale toning to an + /// using Mutate/Clone. + /// + public static class GrayscaleExtensions + { + /// + /// Applies grayscale toning to the image. + /// + /// The image this method extends. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source) + => Grayscale(source, GrayscaleMode.Bt709); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount) + => Grayscale(source, GrayscaleMode.Bt709, amount); + + /// + /// Applies grayscale toning to the image with the given . + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode) + => Grayscale(source, mode, 1F); + + /// + /// Applies grayscale toning to the image with the given using the given amount. + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(amount); + + source.ApplyProcessor(processor); + return source; + } + + /// + /// Applies grayscale toning to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, Rectangle rectangle) + => Grayscale(source, 1F, rectangle); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount, Rectangle rectangle) + => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); + + /// + /// Applies grayscale toning to the image. + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, Rectangle rectangle) + => Grayscale(source, mode, 1F, rectangle); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount, Rectangle rectangle) + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(amount); + + source.ApplyProcessor(processor, rectangle); + return source; + } + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs new file mode 100644 index 0000000000..ef1fa2a6ec --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the alteration of the hue component of an + /// using Mutate/Clone. + /// + public static class HueExtensions + { + /// + /// Alters the hue component of the image. + /// + /// The image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// The to allow chaining of operations. + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) + => source.ApplyProcessor(new HueProcessor(degrees)); + + /// + /// Alters the hue component of the image. + /// + /// The image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) + => source.ApplyProcessor(new HueProcessor(degrees), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs new file mode 100644 index 0000000000..0642db849d --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the inversion of colors of an + /// using Mutate/Clone. + /// + public static class InvertExtensions + { + /// + /// Inverts the colors of the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Invert(this IImageProcessingContext source) + => source.ApplyProcessor(new InvertProcessor(1F)); + + /// + /// Inverts the colors of the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Invert(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new InvertProcessor(1F), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs new file mode 100644 index 0000000000..eadbde7bc0 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the recreation of an old Kodachrome camera effect on an + /// using Mutate/Clone. + /// + public static class KodachromeExtensions + { + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) + => source.ApplyProcessor(new KodachromeProcessor()); + + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Kodachrome(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new KodachromeProcessor(), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs new file mode 100644 index 0000000000..d68cb6aacb --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the alteration of the lightness component of an + /// using Mutate/Clone. + /// + public static class LightnessExtensions + { + /// + /// Alters the lightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new LightnessProcessor(amount)); + + /// + /// Alters the lightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new LightnessProcessor(amount), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs new file mode 100644 index 0000000000..3f8a67feb3 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the recreation of an old Lomograph camera effect on an + /// using Mutate/Clone. + /// + public static class LomographExtensions + { + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Lomograph(this IImageProcessingContext source) + => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions())); + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs new file mode 100644 index 0000000000..2cf6085f34 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the alteration of the opacity component of an + /// using Mutate/Clone. + /// + public static class OpacityExtensions + { + /// + /// Multiplies the alpha component of the image. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new OpacityProcessor(amount)); + + /// + /// Multiplies the alpha component of the image. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs new file mode 100644 index 0000000000..ab75ea56b5 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the recreation of an old Polaroid camera effect on an + /// using Mutate/Clone. + /// + public static class PolaroidExtensions + { + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Polaroid(this IImageProcessingContext source) + => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions())); + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs new file mode 100644 index 0000000000..f68c424bdc --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the alteration of the saturation component of an + /// using Mutate/Clone. + /// + public static class SaturateExtensions + { + /// + /// Alters the saturation component of the image. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new SaturateProcessor(amount)); + + /// + /// Alters the saturation component of the image. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs new file mode 100644 index 0000000000..629ba03e77 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Filters; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of sepia toning on an + /// using Mutate/Clone. + /// + public static class SepiaExtensions + { + /// + /// Applies sepia toning to the image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source) + => Sepia(source, 1F); + + /// + /// Applies sepia toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new SepiaProcessor(amount)); + + /// + /// Applies sepia toning to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, Rectangle rectangle) + => Sepia(source, 1F, rectangle); + + /// + /// Applies sepia toning to the image. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs deleted file mode 100644 index 858e3213b1..0000000000 --- a/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines Gaussian blurring extensions to apply on an - /// using Mutate/Clone. - /// - public static class GaussianBlurExtensions - { - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new GaussianBlurProcessor()); - - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); - - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/GaussianSharpenExtensions.cs deleted file mode 100644 index 79f4a0cc30..0000000000 --- a/src/ImageSharp/Processing/Extensions/GaussianSharpenExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines Gaussian sharpening extensions to apply on an - /// using Mutate/Clone. - /// - public static class GaussianSharpenExtensions - { - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => - source.ApplyProcessor(new GaussianSharpenProcessor()); - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) => - source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen( - this IImageProcessingContext source, - float sigma, - Rectangle rectangle) => - source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/GlowExtensions.cs deleted file mode 100644 index 39734882b0..0000000000 --- a/src/ImageSharp/Processing/Extensions/GlowExtensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of a radial glow on an - /// using Mutate/Clone. - /// - public static class GlowExtensions - { - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source) => - Glow(source, GraphicsOptions.Default); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The color to set as the glow. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) - { - return Glow(source, GraphicsOptions.Default, color); - } - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The the radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => - Glow(source, GraphicsOptions.Default, radius); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) => - source.Glow(GraphicsOptions.Default, rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - Color color, - float radius, - Rectangle rectangle) => - source.Glow(GraphicsOptions.Default, color, ValueSize.Absolute(radius), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options) => - source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f)); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.Glow(options, color, ValueSize.PercentageOfWidth(0.5f)); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The the radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - float radius) => - source.Glow(options, Color.Black, ValueSize.Absolute(radius)); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Rectangle rectangle) => - source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float radius, - Rectangle rectangle) => - source.Glow(options, color, ValueSize.Absolute(radius), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - private static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radius, - Rectangle rectangle) => - source.ApplyProcessor(new GlowProcessor(color, radius, options), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// The to allow chaining of operations. - private static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radius) => - source.ApplyProcessor(new GlowProcessor(color, radius, options)); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs deleted file mode 100644 index d87c40226c..0000000000 --- a/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of grayscale toning to an - /// using Mutate/Clone. - /// - public static class GrayscaleExtensions - { - /// - /// Applies grayscale toning to the image. - /// - /// The image this method extends. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source) - => Grayscale(source, GrayscaleMode.Bt709); - - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount) - => Grayscale(source, GrayscaleMode.Bt709, amount); - - /// - /// Applies grayscale toning to the image with the given . - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode) - => Grayscale(source, mode, 1F); - - /// - /// Applies grayscale toning to the image with the given using the given amount. - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) - { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(amount); - - source.ApplyProcessor(processor); - return source; - } - - /// - /// Applies grayscale toning to the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, Rectangle rectangle) - => Grayscale(source, 1F, rectangle); - - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount, Rectangle rectangle) - => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); - - /// - /// Applies grayscale toning to the image. - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, Rectangle rectangle) - => Grayscale(source, mode, 1F, rectangle); - - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount, Rectangle rectangle) - { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(amount); - - source.ApplyProcessor(processor, rectangle); - return source; - } - } -} diff --git a/src/ImageSharp/Processing/Extensions/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/HueExtensions.cs deleted file mode 100644 index 3955ea7f6e..0000000000 --- a/src/ImageSharp/Processing/Extensions/HueExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the alteration of the hue component of an - /// using Mutate/Clone. - /// - public static class HueExtensions - { - /// - /// Alters the hue component of the image. - /// - /// The image this method extends. - /// The rotation angle in degrees to adjust the hue. - /// The to allow chaining of operations. - public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) - => source.ApplyProcessor(new HueProcessor(degrees)); - - /// - /// Alters the hue component of the image. - /// - /// The image this method extends. - /// The rotation angle in degrees to adjust the hue. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) - => source.ApplyProcessor(new HueProcessor(degrees), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs deleted file mode 100644 index 16c7a89178..0000000000 --- a/src/ImageSharp/Processing/Extensions/InvertExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the inversion of colors of an - /// using Mutate/Clone. - /// - public static class InvertExtensions - { - /// - /// Inverts the colors of the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Invert(this IImageProcessingContext source) - => source.ApplyProcessor(new InvertProcessor(1F)); - - /// - /// Inverts the colors of the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Invert(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new InvertProcessor(1F), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs deleted file mode 100644 index 6c9b279835..0000000000 --- a/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the recreation of an old Kodachrome camera effect on an - /// using Mutate/Clone. - /// - public static class KodachromeExtensions - { - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) - => source.ApplyProcessor(new KodachromeProcessor()); - - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Kodachrome(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new KodachromeProcessor(), rectangle); - } -} diff --git a/src/ImageSharp/Processing/Extensions/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs deleted file mode 100644 index c2b6ac0804..0000000000 --- a/src/ImageSharp/Processing/Extensions/LomographExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the recreation of an old Lomograph camera effect on an - /// using Mutate/Clone. - /// - public static class LomographExtensions - { - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Lomograph(this IImageProcessingContext source) - => source.ApplyProcessor(new LomographProcessor()); - - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new LomographProcessor(), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/HistogramEqualizationExtensions.cs b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/HistogramEqualizationExtensions.cs rename to src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/OilPaintExtensions.cs b/src/ImageSharp/Processing/Extensions/OilPaintExtensions.cs deleted file mode 100644 index 1aa98c8c1b..0000000000 --- a/src/ImageSharp/Processing/Extensions/OilPaintExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Effects; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines oil painting effect extensions applicable on an - /// using Mutate/Clone. - /// - public static class OilPaintExtensions - { - /// - /// Alters the colors of the image recreating an oil painting effect with levels and brushSize - /// set to 10 and 15 respectively. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext OilPaint(this IImageProcessingContext source) => OilPaint(source, 10, 15); - - /// - /// Alters the colors of the image recreating an oil painting effect with levels and brushSize - /// set to 10 and 15 respectively. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext OilPaint(this IImageProcessingContext source, Rectangle rectangle) => - OilPaint(source, 10, 15, rectangle); - - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The image this method extends. - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// The number of neighboring pixels used in calculating each individual pixel value. - /// The to allow chaining of operations. - public static IImageProcessingContext - OilPaint(this IImageProcessingContext source, int levels, int brushSize) => - source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize)); - - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The image this method extends. - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// The number of neighboring pixels used in calculating each individual pixel value. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext OilPaint( - this IImageProcessingContext source, - int levels, - int brushSize, - Rectangle rectangle) => - source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs deleted file mode 100644 index 9c67113ecf..0000000000 --- a/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the alteration of the opacity component of an - /// using Mutate/Clone. - /// - public static class OpacityExtensions - { - /// - /// Multiplies the alpha component of the image. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new OpacityProcessor(amount)); - - /// - /// Multiplies the alpha component of the image. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs new file mode 100644 index 0000000000..21e244f0a3 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Overlays; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extension methods to replace the background color of an + /// using Mutate/Clone. + /// + public static class BackgroundColorExtensions + { + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The color to set as the background. + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => + BackgroundColor(source, source.GetGraphicsOptions(), color); + + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + Color color, + Rectangle rectangle) => + BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle); + + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + source.ApplyProcessor(new BackgroundColorProcessor(options, color)); + + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + Rectangle rectangle) => + source.ApplyProcessor(new BackgroundColorProcessor(options, color), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs new file mode 100644 index 0000000000..c3ce32e636 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Overlays; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of a radial glow on an + /// using Mutate/Clone. + /// + public static class GlowExtensions + { + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source) => + Glow(source, source.GetGraphicsOptions()); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The color to set as the glow. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) + { + return Glow(source, source.GetGraphicsOptions(), color); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The the radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => + Glow(source, source.GetGraphicsOptions(), radius); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) => + source.Glow(source.GetGraphicsOptions(), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + Color color, + float radius, + Rectangle rectangle) => + source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options) => + source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f)); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + source.Glow(options, color, ValueSize.PercentageOfWidth(0.5f)); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The the radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + float radius) => + source.Glow(options, Color.Black, ValueSize.Absolute(radius)); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Rectangle rectangle) => + source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float radius, + Rectangle rectangle) => + source.Glow(options, color, ValueSize.Absolute(radius), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + private static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radius, + Rectangle rectangle) => + source.ApplyProcessor(new GlowProcessor(options, color, radius), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// The to allow chaining of operations. + private static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radius) => + source.ApplyProcessor(new GlowProcessor(options, color, radius)); + } +} diff --git a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs new file mode 100644 index 0000000000..b53880fc12 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Overlays; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of a radial glow to an + /// using Mutate/Clone. + /// + public static class VignetteExtensions + { + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source) => + Vignette(source, source.GetGraphicsOptions()); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The color to set as the vignette. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => + Vignette(source, source.GetGraphicsOptions(), color); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + float radiusX, + float radiusY) => + Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) => + Vignette(source, source.GetGraphicsOptions(), rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + Color color, + float radiusX, + float radiusY, + Rectangle rectangle) => + source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options) => + source.VignetteInternal( + options, + Color.Black, + ValueSize.PercentageOfWidth(.5f), + ValueSize.PercentageOfHeight(.5f)); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + source.VignetteInternal( + options, + color, + ValueSize.PercentageOfWidth(.5f), + ValueSize.PercentageOfHeight(.5f)); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The the x-radius. + /// The the y-radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + float radiusX, + float radiusY) => + source.VignetteInternal(options, Color.Black, radiusX, radiusY); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + Rectangle rectangle) => + source.VignetteInternal( + options, + Color.Black, + ValueSize.PercentageOfWidth(.5f), + ValueSize.PercentageOfHeight(.5f), + rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float radiusX, + float radiusY, + Rectangle rectangle) => + source.VignetteInternal(options, color, radiusX, radiusY, rectangle); + + private static IImageProcessingContext VignetteInternal( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radiusX, + ValueSize radiusY, + Rectangle rectangle) => + source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY), rectangle); + + private static IImageProcessingContext VignetteInternal( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radiusX, + ValueSize radiusY) => + source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY)); + } +} diff --git a/src/ImageSharp/Processing/Extensions/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/PadExtensions.cs deleted file mode 100644 index 270380084f..0000000000 --- a/src/ImageSharp/Processing/Extensions/PadExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of padding operations on an - /// using Mutate/Clone. - /// - public static class PadExtensions - { - /// - /// Evenly pads an image to fit the new dimensions. - /// - /// The source image to pad. - /// The new width. - /// The new height. - /// The to allow chaining of operations. - public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) - => source.Pad(width, height, default); - - /// - /// Evenly pads an image to fit the new dimensions with the given background color. - /// - /// The source image to pad. - /// The new width. - /// The new height. - /// The background color with which to pad the image. - /// The to allow chaining of operations. - public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) - { - var options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.BoxPad, - Sampler = KnownResamplers.NearestNeighbor, - }; - - return color.Equals(default) ? source.Resize(options) : source.Resize(options).BackgroundColor(color); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/PixelateExtensions.cs b/src/ImageSharp/Processing/Extensions/PixelateExtensions.cs deleted file mode 100644 index bf40af91ad..0000000000 --- a/src/ImageSharp/Processing/Extensions/PixelateExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Effects; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines pixelation effect extensions applicable on an - /// using Mutate/Clone. - /// - public static class PixelateExtensions - { - /// - /// Pixelates an image with the given pixel size. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); - - /// - /// Pixelates an image with the given pixel size. - /// - /// The image this method extends. - /// The size of the pixels. - /// The to allow chaining of operations. - public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => - source.ApplyProcessor(new PixelateProcessor(size)); - - /// - /// Pixelates an image with the given pixel size. - /// - /// The image this method extends. - /// The size of the pixels. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Pixelate( - this IImageProcessingContext source, - int size, - Rectangle rectangle) => - source.ApplyProcessor(new PixelateProcessor(size), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs deleted file mode 100644 index 6b6d43d5b8..0000000000 --- a/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the recreation of an old Polaroid camera effect on an - /// using Mutate/Clone. - /// - public static class PolaroidExtensions - { - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Polaroid(this IImageProcessingContext source) - => source.ApplyProcessor(new PolaroidProcessor()); - - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new PolaroidProcessor(), rectangle); - } -} diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs index 40b1c439e6..45cff93982 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs @@ -19,13 +19,32 @@ namespace SixLabors.ImageSharp.Processing /// /// The image to mutate. /// The operation to perform on the source. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. public static void Mutate(this Image source, Action operation) + => Mutate(source, source.GetConfiguration(), operation); + + /// + /// Mutates the source image by applying the image operation to it. + /// + /// The image to mutate. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the source. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Configuration configuration, Action operation) { + Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); Guard.NotNull(operation, nameof(operation)); source.EnsureNotDisposed(); - source.AcceptVisitor(new ProcessingVisitor(operation, true)); + source.AcceptVisitor(new ProcessingVisitor(configuration, operation, true)); } /// @@ -34,16 +53,36 @@ namespace SixLabors.ImageSharp.Processing /// The pixel format. /// The image to mutate. /// The operation to perform on the source. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. public static void Mutate(this Image source, Action operation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + => Mutate(source, source.GetConfiguration(), operation); + + /// + /// Mutates the source image by applying the image operation to it. + /// + /// The pixel format. + /// The image to mutate. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the source. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Configuration configuration, Action operation) + where TPixel : unmanaged, IPixel { + Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); Guard.NotNull(operation, nameof(operation)); source.EnsureNotDisposed(); IInternalImageProcessingContext operationsRunner - = source.GetConfiguration() - .ImageOperationsProvider.CreateImageProcessingContext(source, true); + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); operation(operationsRunner); } @@ -54,16 +93,35 @@ namespace SixLabors.ImageSharp.Processing /// The pixel format. /// The image to mutate. /// The operations to perform on the source. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. public static void Mutate(this Image source, params IImageProcessor[] operations) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + => Mutate(source, source.GetConfiguration(), operations); + + /// + /// Mutates the source image by applying the operations to it. + /// + /// The pixel format. + /// The image to mutate. + /// The configuration which allows altering default behaviour or extending the library. + /// The operations to perform on the source. + /// The configuration is null. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Configuration configuration, params IImageProcessor[] operations) + where TPixel : unmanaged, IPixel { Guard.NotNull(source, nameof(source)); Guard.NotNull(operations, nameof(operations)); source.EnsureNotDisposed(); IInternalImageProcessingContext operationsRunner - = source.GetConfiguration() - .ImageOperationsProvider.CreateImageProcessingContext(source, true); + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); operationsRunner.ApplyProcessors(operations); } @@ -73,14 +131,34 @@ namespace SixLabors.ImageSharp.Processing /// /// The image to clone. /// The operation to perform on the clone. - /// The new . + /// The new . + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. public static Image Clone(this Image source, Action operation) + => Clone(source, source.GetConfiguration(), operation); + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The image to clone. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the clone. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + /// The new . + public static Image Clone(this Image source, Configuration configuration, Action operation) { + Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); Guard.NotNull(operation, nameof(operation)); source.EnsureNotDisposed(); - var visitor = new ProcessingVisitor(operation, false); + var visitor = new ProcessingVisitor(configuration, operation, false); source.AcceptVisitor(visitor); return visitor.ResultImage; } @@ -91,17 +169,38 @@ namespace SixLabors.ImageSharp.Processing /// The pixel format. /// The image to clone. /// The operation to perform on the clone. - /// The new + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + /// The new . public static Image Clone(this Image source, Action operation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + => Clone(source, source.GetConfiguration(), operation); + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The pixel format. + /// The image to clone. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the clone. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + /// The new + public static Image Clone(this Image source, Configuration configuration, Action operation) + where TPixel : unmanaged, IPixel { + Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); Guard.NotNull(operation, nameof(operation)); source.EnsureNotDisposed(); IInternalImageProcessingContext operationsRunner - = source.GetConfiguration() - .ImageOperationsProvider.CreateImageProcessingContext(source, false); + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); operation(operationsRunner); return operationsRunner.GetResultImage(); @@ -113,17 +212,38 @@ namespace SixLabors.ImageSharp.Processing /// The pixel format. /// The image to clone. /// The operations to perform on the clone. - /// The new + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + /// The new public static Image Clone(this Image source, params IImageProcessor[] operations) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + => Clone(source, source.GetConfiguration(), operations); + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operations. + /// + /// The pixel format. + /// The image to clone. + /// The configuration which allows altering default behaviour or extending the library. + /// The operations to perform on the clone. + /// The configuration is null. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + /// The new + public static Image Clone(this Image source, Configuration configuration, params IImageProcessor[] operations) + where TPixel : unmanaged, IPixel { + Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); Guard.NotNull(operations, nameof(operations)); source.EnsureNotDisposed(); IInternalImageProcessingContext operationsRunner - = source.GetConfiguration() - .ImageOperationsProvider.CreateImageProcessingContext(source, false); + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); operationsRunner.ApplyProcessors(operations); return operationsRunner.GetResultImage(); @@ -134,6 +254,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The image processing context. /// The operations to perform on the source. + /// The processing operation failed. /// The to allow chaining of operations. public static IImageProcessingContext ApplyProcessors( this IImageProcessingContext source, @@ -149,12 +270,15 @@ namespace SixLabors.ImageSharp.Processing private class ProcessingVisitor : IImageVisitor { + private readonly Configuration configuration; + private readonly Action operation; private readonly bool mutate; - public ProcessingVisitor(Action operation, bool mutate) + public ProcessingVisitor(Configuration configuration, Action operation, bool mutate) { + this.configuration = configuration; this.operation = operation; this.mutate = mutate; } @@ -162,11 +286,10 @@ namespace SixLabors.ImageSharp.Processing public Image ResultImage { get; private set; } public void Visit(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IInternalImageProcessingContext operationsRunner = - image.GetConfiguration() - .ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate); + this.configuration.ImageOperationsProvider.CreateImageProcessingContext(this.configuration, image, this.mutate); this.operation(operationsRunner); this.ResultImage = operationsRunner.GetResultImage(); diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs new file mode 100644 index 0000000000..86ccddd856 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of quantizing algorithms on an + /// using Mutate/Clone. + /// + public static class QuantizeExtensions + { + /// + /// Applies quantization to the image using the . + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source) => + Quantize(source, KnownQuantizers.Octree); + + /// + /// Applies quantization to the image. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => + source.ApplyProcessor(new QuantizeProcessor(quantizer)); + + /// + /// Applies quantization to the image using the . + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => + Quantize(source, KnownQuantizers.Octree, rectangle); + + /// + /// Applies quantization to the image. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer, Rectangle rectangle) => + source.ApplyProcessor(new QuantizeProcessor(quantizer), rectangle); + } +} diff --git a/src/ImageSharp/Processing/Extensions/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/QuantizeExtensions.cs deleted file mode 100644 index 3410ee6bec..0000000000 --- a/src/ImageSharp/Processing/Extensions/QuantizeExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of quantizing algorithms on an - /// using Mutate/Clone. - /// - public static class QuantizeExtensions - { - /// - /// Applies quantization to the image using the . - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Quantize(this IImageProcessingContext source) => - Quantize(source, KnownQuantizers.Octree); - - /// - /// Applies quantization to the image. - /// - /// The image this method extends. - /// The quantizer to apply to perform the operation. - /// The to allow chaining of operations. - public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => - source.ApplyProcessor(new QuantizeProcessor(quantizer)); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs deleted file mode 100644 index f494ed9094..0000000000 --- a/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of resize operations on an - /// using Mutate/Clone. - /// - public static class ResizeExtensions - { - /// - /// Resizes an image to the given . - /// - /// The image to resize. - /// The target image size. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) - => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); - - /// - /// Resizes an image to the given . - /// - /// The image to resize. - /// The target image size. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) - => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); - - /// - /// Resizes an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) - => Resize(source, width, height, KnownResamplers.Bicubic, false); - - /// - /// Resizes an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) - => Resize(source, width, height, KnownResamplers.Bicubic, compand); - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) - => Resize(source, width, height, sampler, false); - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image size. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) - => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) - => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); - - /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize( - this IImageProcessingContext source, - int width, - int height, - IResampler sampler, - Rectangle sourceRectangle, - Rectangle targetRectangle, - bool compand) - { - var options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.Manual, - Sampler = sampler, - TargetRectangle = targetRectangle, - Compand = compand - }; - - return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle); - } - - /// - /// Resizes an image to the given width and height with the given sampler and source rectangle. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize( - this IImageProcessingContext source, - int width, - int height, - IResampler sampler, - Rectangle targetRectangle, - bool compand) - { - var options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.Manual, - Sampler = sampler, - TargetRectangle = targetRectangle, - Compand = compand - }; - - return Resize(source, options); - } - - /// - /// Resizes an image in accordance with the given . - /// - /// The image to resize. - /// The resize options. - /// The to allow chaining of operations. - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) - => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); - } -} diff --git a/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs deleted file mode 100644 index a94a9a407d..0000000000 --- a/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the alteration of the saturation component of an - /// using Mutate/Clone. - /// - public static class SaturateExtensions - { - /// - /// Alters the saturation component of the image. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new SaturateProcessor(amount)); - - /// - /// Alters the saturation component of the image. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs deleted file mode 100644 index df32307f47..0000000000 --- a/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of sepia toning on an - /// using Mutate/Clone. - /// - public static class SepiaExtensions - { - /// - /// Applies sepia toning to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Sepia(this IImageProcessingContext source) - => Sepia(source, 1F); - - /// - /// Applies sepia toning to the image using the given amount. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new SepiaProcessor(amount)); - - /// - /// Applies sepia toning to the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Sepia(this IImageProcessingContext source, Rectangle rectangle) - => Sepia(source, 1F, rectangle); - - /// - /// Applies sepia toning to the image. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/TransformExtensions.cs deleted file mode 100644 index 7fffb71d20..0000000000 --- a/src/ImageSharp/Processing/Extensions/TransformExtensions.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of composable transform operations on an - /// using Mutate/Clone. - /// - public static class TransformExtensions - { - /// - /// Performs an affine transform of an image. - /// - /// The image to transform. - /// The affine transform builder. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - AffineTransformBuilder builder) => - Transform(source, builder, KnownResamplers.Bicubic); - - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The . - /// The affine transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - AffineTransformBuilder builder, - IResampler sampler) => - ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); - - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The affine transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - AffineTransformBuilder builder, - IResampler sampler) - { - Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); - return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); - } - - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The transformation matrix. - /// The size of the result image. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - Matrix3x2 transform, - Size targetDimensions, - IResampler sampler) - { - return ctx.ApplyProcessor( - new AffineTransformProcessor(transform, sampler, targetDimensions), - sourceRectangle); - } - - /// - /// Performs a projective transform of an image. - /// - /// The image to transform. - /// The affine transform builder. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - ProjectiveTransformBuilder builder) => - Transform(source, builder, KnownResamplers.Bicubic); - - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The . - /// The projective transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - ProjectiveTransformBuilder builder, - IResampler sampler) => - ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); - - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The projective transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - ProjectiveTransformBuilder builder, - IResampler sampler) - { - Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); - return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); - } - - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The transformation matrix. - /// The size of the result image. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - Matrix4x4 transform, - Size targetDimensions, - IResampler sampler) - { - return ctx.ApplyProcessor( - new ProjectiveTransformProcessor(transform, sampler, targetDimensions), - sourceRectangle); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs rename to src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs new file mode 100644 index 0000000000..5fc8125ea8 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of cropping operations on an + /// using Mutate/Clone. + /// + public static class CropExtensions + { + /// + /// Crops an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to allow chaining of operations. + public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => + Crop(source, new Rectangle(0, 0, width, height)); + + /// + /// Crops an image to the given rectangle. + /// + /// The image to crop. + /// + /// The structure that specifies the portion of the image object to retain. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => + source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/EntropyCropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/EntropyCropExtensions.cs rename to src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/FlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/FlipExtensions.cs rename to src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs new file mode 100644 index 0000000000..33a6fc36d6 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of padding operations on an + /// using Mutate/Clone. + /// + public static class PadExtensions + { + /// + /// Evenly pads an image to fit the new dimensions. + /// + /// The source image to pad. + /// The new width. + /// The new height. + /// The to allow chaining of operations. + public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) + => source.Pad(width, height, default); + + /// + /// Evenly pads an image to fit the new dimensions with the given background color. + /// + /// The source image to pad. + /// The new width. + /// The new height. + /// The background color with which to pad the image. + /// The to allow chaining of operations. + public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) + { + var options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.BoxPad, + Sampler = KnownResamplers.NearestNeighbor, + }; + + return color.Equals(default) ? source.Resize(options) : source.Resize(options).BackgroundColor(color); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs new file mode 100644 index 0000000000..882b177214 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of resize operations on an + /// using Mutate/Clone. + /// + public static class ResizeExtensions + { + /// + /// Resizes an image to the given . + /// + /// The image to resize. + /// The target image size. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); + + /// + /// Resizes an image to the given . + /// + /// The image to resize. + /// The target image size. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); + + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) + => Resize(source, width, height, KnownResamplers.Bicubic, false); + + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) + => Resize(source, width, height, KnownResamplers.Bicubic, compand); + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) + => Resize(source, width, height, sampler, false); + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image size. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) + => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) + => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); + + /// + /// Resizes an image to the given width and height with the given sampler and + /// source rectangle. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle sourceRectangle, + Rectangle targetRectangle, + bool compand) + { + var options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.Manual, + Sampler = sampler, + TargetRectangle = targetRectangle, + Compand = compand + }; + + return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle); + } + + /// + /// Resizes an image to the given width and height with the given sampler and source rectangle. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle targetRectangle, + bool compand) + { + var options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.Manual, + Sampler = sampler, + TargetRectangle = targetRectangle, + Compand = compand + }; + + return Resize(source, options); + } + + /// + /// Resizes an image in accordance with the given . + /// + /// The image to resize. + /// The resize options. + /// The to allow chaining of operations. + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) + => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); + } +} diff --git a/src/ImageSharp/Processing/Extensions/RotateExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/RotateExtensions.cs rename to src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs rename to src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/SkewExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs similarity index 100% rename from src/ImageSharp/Processing/Extensions/SkewExtensions.cs rename to src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs new file mode 100644 index 0000000000..ee8f3854e8 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs @@ -0,0 +1,144 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the application of composable transform operations on an + /// using Mutate/Clone. + /// + public static class TransformExtensions + { + /// + /// Performs an affine transform of an image. + /// + /// The image to transform. + /// The affine transform builder. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + AffineTransformBuilder builder) => + Transform(source, builder, KnownResamplers.Bicubic); + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The affine transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + AffineTransformBuilder builder, + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The affine transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + AffineTransformBuilder builder, + IResampler sampler) + { + Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix3x2 transform, + Size targetDimensions, + IResampler sampler) + { + return ctx.ApplyProcessor( + new AffineTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } + + /// + /// Performs a projective transform of an image. + /// + /// The image to transform. + /// The affine transform builder. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + ProjectiveTransformBuilder builder) => + Transform(source, builder, KnownResamplers.Bicubic); + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The projective transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + ProjectiveTransformBuilder builder, + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The projective transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + ProjectiveTransformBuilder builder, + IResampler sampler) + { + Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix4x4 transform, + Size targetDimensions, + IResampler sampler) + { + return ctx.ApplyProcessor( + new ProjectiveTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs deleted file mode 100644 index 74a59d3e13..0000000000 --- a/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extensions that allow the application of a radial glow to an - /// using Mutate/Clone. - /// - public static class VignetteExtensions - { - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source) => - Vignette(source, GraphicsOptions.Default); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The color to set as the vignette. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => - Vignette(source, GraphicsOptions.Default, color); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The the x-radius. - /// The the y-radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - float radiusX, - float radiusY) => - Vignette(source, GraphicsOptions.Default, radiusX, radiusY); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) => - Vignette(source, GraphicsOptions.Default, rectangle); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The color to set as the vignette. - /// The the x-radius. - /// The the y-radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - Color color, - float radiusX, - float radiusY, - Rectangle rectangle) => - source.Vignette(GraphicsOptions.Default, color, radiusX, radiusY, rectangle); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options) => - source.VignetteInternal( - options, - Color.Black, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f)); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the vignette. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.VignetteInternal( - options, - color, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f)); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The the x-radius. - /// The the y-radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - float radiusX, - float radiusY) => - source.VignetteInternal(options, Color.Black, radiusX, radiusY); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Rectangle rectangle) => - source.VignetteInternal( - options, - Color.Black, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f), - rectangle); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the vignette. - /// The the x-radius. - /// The the y-radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float radiusX, - float radiusY, - Rectangle rectangle) => - source.VignetteInternal(options, color, radiusX, radiusY, rectangle); - - private static IImageProcessingContext VignetteInternal( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radiusX, - ValueSize radiusY, - Rectangle rectangle) => - source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options), rectangle); - - private static IImageProcessingContext VignetteInternal( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radiusX, - ValueSize radiusY) => - source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options)); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/IImageProcessingContext.cs b/src/ImageSharp/Processing/IImageProcessingContext.cs index 509b1313d9..cb39766a94 100644 --- a/src/ImageSharp/Processing/IImageProcessingContext.cs +++ b/src/ImageSharp/Processing/IImageProcessingContext.cs @@ -1,9 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { @@ -13,10 +12,15 @@ namespace SixLabors.ImageSharp.Processing public interface IImageProcessingContext { /// - /// Gets a reference to the used to allocate buffers - /// for this context. + /// Gets the configuration which allows altering default behaviour or extending the library. /// - MemoryAllocator MemoryAllocator { get; } + Configuration Configuration { get; } + + /// + /// Gets a set of properties for the Image Processing Context. + /// + /// This can be used for storing global settings and defaults to be accessable to processors. + IDictionary Properties { get; } /// /// Gets the image dimensions at the current point in the processing pipeline. @@ -39,4 +43,4 @@ namespace SixLabors.ImageSharp.Processing /// The current operations class to allow chaining of operations. IImageProcessingContext ApplyProcessor(IImageProcessor processor); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs index 948e70b445..5394fac8b9 100644 --- a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs +++ b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -14,11 +14,12 @@ namespace SixLabors.ImageSharp.Processing /// Called during mutate operations to generate the image operations provider. /// /// The pixel format + /// The configuration which allows altering default behaviour or extending the library. /// The source image. /// A flag to determine whether image operations are allowed to mutate the source image. /// A new - IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) - where TPixel : struct, IPixel; + IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel; } /// @@ -27,10 +28,10 @@ namespace SixLabors.ImageSharp.Processing internal class DefaultImageOperationsProviderFactory : IImageProcessingContextFactory { /// - public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) - where TPixel : struct, IPixel + public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel { - return new DefaultImageProcessorContext(source, mutate); + return new DefaultImageProcessorContext(configuration, source, mutate); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs index 55303b1ef9..a39483b0d9 100644 --- a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs +++ b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -10,13 +10,13 @@ namespace SixLabors.ImageSharp.Processing /// /// The pixel type. internal interface IInternalImageProcessingContext : IImageProcessingContext - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// - /// Returns the result image to return by + /// Returns the result image to return by /// (and other overloads). /// /// The current image or a new image depending on whether it is requested to mutate the source image. Image GetResultImage(); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/KnownDiffusers.cs b/src/ImageSharp/Processing/KnownDiffusers.cs deleted file mode 100644 index 2b10312fee..0000000000 --- a/src/ImageSharp/Processing/KnownDiffusers.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Contains reusable static instances of known error diffusion algorithms - /// - public static class KnownDiffusers - { - /// - /// Gets the error diffuser that implements the Atkinson algorithm. - /// - public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); - - /// - /// Gets the error diffuser that implements the Burks algorithm. - /// - public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); - - /// - /// Gets the error diffuser that implements the Floyd-Steinberg algorithm. - /// - public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); - - /// - /// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm. - /// - public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); - - /// - /// Gets the error diffuser that implements the Sierra-2 algorithm. - /// - public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); - - /// - /// Gets the error diffuser that implements the Sierra-3 algorithm. - /// - public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); - - /// - /// Gets the error diffuser that implements the Sierra-Lite algorithm. - /// - public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); - - /// - /// Gets the error diffuser that implements the Stevenson-Arce algorithm. - /// - public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); - - /// - /// Gets the error diffuser that implements the Stucki algorithm. - /// - public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDitherers.cs b/src/ImageSharp/Processing/KnownDitherers.cs deleted file mode 100644 index dad5bb38c7..0000000000 --- a/src/ImageSharp/Processing/KnownDitherers.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Contains reusable static instances of known ordered dither matrices - /// - public static class KnownDitherers - { - /// - /// Gets the order ditherer using the 2x2 Bayer dithering matrix - /// - public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); - - /// - /// Gets the order ditherer using the 3x3 dithering matrix - /// - public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); - - /// - /// Gets the order ditherer using the 4x4 Bayer dithering matrix - /// - public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); - - /// - /// Gets the order ditherer using the 8x8 Bayer dithering matrix - /// - public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDitherings.cs b/src/ImageSharp/Processing/KnownDitherings.cs new file mode 100644 index 0000000000..bb968d2ef5 --- /dev/null +++ b/src/ImageSharp/Processing/KnownDitherings.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Contains reusable static instances of known dithering algorithms. + /// + public static class KnownDitherings + { + /// + /// Gets the order ditherer using the 2x2 Bayer dithering matrix + /// + public static IDither Bayer2x2 { get; } = OrderedDither.Bayer2x2; + + /// + /// Gets the order ditherer using the 3x3 dithering matrix + /// + public static IDither Ordered3x3 { get; } = OrderedDither.Ordered3x3; + + /// + /// Gets the order ditherer using the 4x4 Bayer dithering matrix + /// + public static IDither Bayer4x4 { get; } = OrderedDither.Bayer4x4; + + /// + /// Gets the order ditherer using the 8x8 Bayer dithering matrix + /// + public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8; + + /// + /// Gets the error Dither that implements the Atkinson algorithm. + /// + public static IDither Atkinson { get; } = ErrorDither.Atkinson; + + /// + /// Gets the error Dither that implements the Burks algorithm. + /// + public static IDither Burks { get; } = ErrorDither.Burkes; + + /// + /// Gets the error Dither that implements the Floyd-Steinberg algorithm. + /// + public static IDither FloydSteinberg { get; } = ErrorDither.FloydSteinberg; + + /// + /// Gets the error Dither that implements the Jarvis-Judice-Ninke algorithm. + /// + public static IDither JarvisJudiceNinke { get; } = ErrorDither.JarvisJudiceNinke; + + /// + /// Gets the error Dither that implements the Sierra-2 algorithm. + /// + public static IDither Sierra2 { get; } = ErrorDither.Sierra2; + + /// + /// Gets the error Dither that implements the Sierra-3 algorithm. + /// + public static IDither Sierra3 { get; } = ErrorDither.Sierra3; + + /// + /// Gets the error Dither that implements the Sierra-Lite algorithm. + /// + public static IDither SierraLite { get; } = ErrorDither.SierraLite; + + /// + /// Gets the error Dither that implements the Stevenson-Arce algorithm. + /// + public static IDither StevensonArce { get; } = ErrorDither.StevensonArce; + + /// + /// Gets the error Dither that implements the Stucki algorithm. + /// + public static IDither Stucki { get; } = ErrorDither.Stucki; + } +} diff --git a/src/ImageSharp/Processing/KnownFilterMatrices.cs b/src/ImageSharp/Processing/KnownFilterMatrices.cs index 1f36e2593a..268281e4fe 100644 --- a/src/ImageSharp/Processing/KnownFilterMatrices.cs +++ b/src/ImageSharp/Processing/KnownFilterMatrices.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Primitives; // Many of these matrices are translated from Chromium project where // SkScalar[] is memory-mapped to a row-major matrix. @@ -432,6 +431,32 @@ namespace SixLabors.ImageSharp.Processing return m; } + /// + /// Create a lightness filter matrix using the given amount. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateLightnessFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + amount--; + + return new ColorMatrix + { + M11 = 1F, + M22 = 1F, + M33 = 1F, + M44 = 1F, + M51 = amount, + M52 = amount, + M53 = amount + }; + } + /// /// Create a sepia filter matrix using the given amount. /// The formula used matches the svg specification. diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 621215b280..348c084071 100644 --- a/src/ImageSharp/Processing/KnownResamplers.cs +++ b/src/ImageSharp/Processing/KnownResamplers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -13,86 +13,86 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) /// - public static IResampler Bicubic { get; } = new BicubicResampler(); + public static IResampler Bicubic { get; } = default(BicubicResampler); /// /// Gets the Box sampler that implements the box algorithm. Similar to nearest neighbor when upscaling. /// When downscaling the pixels will average, merging pixels together. /// - public static IResampler Box { get; } = new BoxResampler(); + public static IResampler Box { get; } = default(BoxResampler); /// /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function /// - public static IResampler CatmullRom { get; } = new CatmullRomResampler(); + public static IResampler CatmullRom { get; } = CubicResampler.CatmullRom; /// /// Gets the Hermite sampler. A type of smoothed triangular interpolation filter that rounds off strong edges while /// preserving flat 'color levels' in the original image. /// - public static IResampler Hermite { get; } = new HermiteResampler(); + public static IResampler Hermite { get; } = CubicResampler.Hermite; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 2 pixels. /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos2 { get; } = new Lanczos2Resampler(); + public static IResampler Lanczos2 { get; } = LanczosResampler.Lanczos2; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 3 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos3 { get; } = new Lanczos3Resampler(); + public static IResampler Lanczos3 { get; } = LanczosResampler.Lanczos3; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 5 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos5 { get; } = new Lanczos5Resampler(); + public static IResampler Lanczos5 { get; } = LanczosResampler.Lanczos5; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 8 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos8 { get; } = new Lanczos8Resampler(); + public static IResampler Lanczos8 { get; } = LanczosResampler.Lanczos8; /// /// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between /// detail preservation (sharpness) and smoothness. /// - public static IResampler MitchellNetravali { get; } = new MitchellNetravaliResampler(); + public static IResampler MitchellNetravali { get; } = CubicResampler.MitchellNetravali; /// /// Gets the Nearest-Neighbour sampler that implements the nearest neighbor algorithm. This uses a very fast, unscaled filter /// which will select the closest pixel to the new pixels position. /// - public static IResampler NearestNeighbor { get; } = new NearestNeighborResampler(); + public static IResampler NearestNeighbor { get; } = default(NearestNeighborResampler); /// /// Gets the Robidoux sampler. This algorithm developed by Nicolas Robidoux providing a very good equilibrium between /// detail preservation (sharpness) and smoothness comparable to . /// - public static IResampler Robidoux { get; } = new RobidouxResampler(); + public static IResampler Robidoux { get; } = CubicResampler.Robidoux; /// /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler /// - public static IResampler RobidouxSharp { get; } = new RobidouxSharpResampler(); + public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; /// - /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results. + /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. /// - public static IResampler Spline { get; } = new SplineResampler(); + public static IResampler Spline { get; } = CubicResampler.Spline; /// /// Gets the Triangle sampler, otherwise known as Bilinear. This interpolation algorithm can be used where perfect image transformation /// with pixel matching is impossible, so that one can calculate and assign appropriate intensity values to pixels /// - public static IResampler Triangle { get; } = new TriangleResampler(); + public static IResampler Triangle { get; } = default(TriangleResampler); /// /// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results. /// - public static IResampler Welch { get; } = new WelchResampler(); + public static IResampler Welch { get; } = default(WelchResampler); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/PixelRowOperation.cs b/src/ImageSharp/Processing/PixelRowOperation.cs new file mode 100644 index 0000000000..6857b24f19 --- /dev/null +++ b/src/ImageSharp/Processing/PixelRowOperation.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// A representing a user defined processing delegate to use to modify image rows. + /// + /// The target row of pixels to process. + /// The , , , and fields map the RGBA channels respectively. + public delegate void PixelRowOperation(Span span); + + /// + /// A representing a user defined processing delegate to use to modify image rows. + /// + /// + /// The type of the parameter of the method that this delegate encapsulates. + /// This type parameter is contravariant.That is, you can use either the type you specified or any type that is less derived. + /// + /// The target row of pixels to process. + /// The parameter of the method that this delegate encapsulates. + /// The , , , and fields map the RGBA channels respectively. + public delegate void PixelRowOperation(Span span, T value); +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs new file mode 100644 index 0000000000..3558a94899 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Binarization +{ + /// + /// Performs Bradley Adaptive Threshold filter against an image. + /// + /// + /// Implements "Adaptive Thresholding Using the Integral Image", + /// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf + /// + public class AdaptiveThresholdProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + public AdaptiveThresholdProcessor() + : this(Color.White, Color.Black, 0.85f) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Threshold limit. + public AdaptiveThresholdProcessor(float thresholdLimit) + : this(Color.White, Color.Black, thresholdLimit) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Color for upper threshold. + /// Color for lower threshold. + public AdaptiveThresholdProcessor(Color upper, Color lower) + : this(upper, lower, 0.85f) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Color for upper threshold. + /// Color for lower threshold. + /// Threshold limit. + public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit) + { + this.Upper = upper; + this.Lower = lower; + this.ThresholdLimit = thresholdLimit; + } + + /// + /// Gets or sets upper color limit for thresholding. + /// + public Color Upper { get; set; } + + /// + /// Gets or sets lower color limit for threshold. + /// + public Color Lower { get; set; } + + /// + /// Gets or sets the value for threshold limit. + /// + public float ThresholdLimit { get; set; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new AdaptiveThresholdProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs new file mode 100644 index 0000000000..dd8833ad96 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Binarization +{ + /// + /// Performs Bradley Adaptive Threshold filter against an image. + /// + internal class AdaptiveThresholdProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly AdaptiveThresholdProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + Configuration configuration = this.Configuration; + TPixel upper = this.definition.Upper.ToPixel(); + TPixel lower = this.definition.Lower.ToPixel(); + float thresholdLimit = this.definition.ThresholdLimit; + + int startY = intersect.Y; + int endY = intersect.Bottom; + int startX = intersect.X; + int endX = intersect.Right; + + int width = intersect.Width; + int height = intersect.Height; + + // ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' + byte clusterSize = (byte)Math.Truncate((width / 16f) - 1); + + // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data. + using (Buffer2D intImage = this.Configuration.MemoryAllocator.Allocate2D(width, height)) + { + Rgba32 rgb = default; + for (int x = startX; x < endX; x++) + { + ulong sum = 0; + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); + ref TPixel color = ref Unsafe.Add(ref rowRef, x); + color.ToRgba32(ref rgb); + + sum += (ulong)(rgb.R + rgb.G + rgb.G); + if (x - startX != 0) + { + intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; + } + else + { + intImage[x - startX, y - startY] = sum; + } + } + } + + var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); + ParallelRowIterator.IterateRows( + configuration, + intersect, + in operation); + } + } + + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly Buffer2D intImage; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly float thresholdLimit; + private readonly int startX; + private readonly int endX; + private readonly int startY; + private readonly byte clusterSize; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + ImageFrame source, + Buffer2D intImage, + TPixel upper, + TPixel lower, + float thresholdLimit, + byte clusterSize, + int startX, + int endX, + int startY) + { + this.bounds = bounds; + this.source = source; + this.intImage = intImage; + this.upper = upper; + this.lower = lower; + this.thresholdLimit = thresholdLimit; + this.startX = startX; + this.endX = endX; + this.startY = startY; + this.clusterSize = clusterSize; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Rgba32 rgb = default; + Span pixelRow = this.source.GetPixelRowSpan(y); + + for (int x = this.startX; x < this.endX; x++) + { + TPixel pixel = pixelRow[x]; + pixel.ToRgba32(ref rgb); + + var x1 = Math.Max(x - this.startX - this.clusterSize + 1, 0); + var x2 = Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); + var y1 = Math.Max(y - this.startY - this.clusterSize + 1, 0); + var y2 = Math.Min(y - this.startY + this.clusterSize + 1, this.bounds.Height - 1); + + var count = (uint)((x2 - x1) * (y2 - y1)); + var sum = (long)Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], long.MaxValue); + + if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.thresholdLimit) + { + this.source[x, y] = this.lower; + } + else + { + this.source[x, y] = this.upper; + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs deleted file mode 100644 index 80164793b2..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Performs binary threshold filtering against an image using error diffusion. - /// - public class BinaryErrorDiffusionProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) - : this(diffuser, .5F) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold) - : this(diffuser, threshold, Color.White, Color.Black) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold. - public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, Color upperColor, Color lowerColor) - { - Guard.NotNull(diffuser, nameof(diffuser)); - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - - this.Diffuser = diffuser; - this.Threshold = threshold; - this.UpperColor = upperColor; - this.LowerColor = lowerColor; - } - - /// - /// Gets the error diffuser. - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the threshold value. - /// - public float Threshold { get; } - - /// - /// Gets the color to use for pixels that are above the threshold. - /// - public Color UpperColor { get; } - - /// - /// Gets the color to use for pixels that fall below the threshold. - /// - public Color LowerColor { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new BinaryErrorDiffusionProcessor(this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs deleted file mode 100644 index 7e3458ae3e..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Performs binary threshold filtering against an image using error diffusion. - /// - /// The pixel format. - internal class BinaryErrorDiffusionProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly BinaryErrorDiffusionProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BinaryErrorDiffusionProcessor(BinaryErrorDiffusionProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel upperColor = this.definition.UpperColor.ToPixel(); - TPixel lowerColor = this.definition.LowerColor.ToPixel(); - IErrorDiffuser diffuser = this.definition.Diffuser; - - byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor; - diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs deleted file mode 100644 index 0a426c893a..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Defines a binary threshold filtering using ordered dithering. - /// - public class BinaryOrderedDitherProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - public BinaryOrderedDitherProcessor(IOrderedDither dither) - : this(dither, Color.White, Color.Black) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold. - public BinaryOrderedDitherProcessor(IOrderedDither dither, Color upperColor, Color lowerColor) - { - this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); - this.UpperColor = upperColor; - this.LowerColor = lowerColor; - } - - /// - /// Gets the ditherer. - /// - public IOrderedDither Dither { get; } - - /// - /// Gets the color to use for pixels that are above the threshold. - /// - public Color UpperColor { get; } - - /// - /// Gets the color to use for pixels that fall below the threshold. - /// - public Color LowerColor { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new BinaryOrderedDitherProcessor(this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs deleted file mode 100644 index e0aae0279f..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Performs binary threshold filtering against an image using ordered dithering. - /// - /// The pixel format. - internal class BinaryOrderedDitherProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly BinaryOrderedDitherProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BinaryOrderedDitherProcessor(BinaryOrderedDitherProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - IOrderedDither dither = this.definition.Dither; - TPixel upperColor = this.definition.UpperColor.ToPixel(); - TPixel lowerColor = this.definition.LowerColor.ToPixel(); - - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - dither.Dither(source, sourcePixel, upperColor, lowerColor, luminance, x, y); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index a33c464694..17fb39df3e 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Binarization { @@ -50,8 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public Color LowerColor { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new BinaryThresholdProcessor(this, source, sourceRectangle); + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BinaryThresholdProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 7234955edb..0d363689dd 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -2,11 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Binarization { @@ -15,18 +14,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// The pixel format. internal class BinaryThresholdProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly BinaryThresholdProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public BinaryThresholdProcessor(BinaryThresholdProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.definition = definition; } @@ -42,36 +42,64 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization Configuration configuration = this.Configuration; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; + bool isAlphaOnly = typeof(TPixel) == typeof(A8); + + var operation = new RowOperation(interest, source, upper, lower, threshold, isAlphaOnly); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + /// + /// A implementing the clone logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly byte threshold; + private readonly int minX; + private readonly int maxX; + private readonly bool isAlphaOnly; - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + ImageFrame source, + TPixel upper, + TPixel lower, + byte threshold, + bool isAlphaOnly) + { + this.source = source; + this.upper = upper; + this.lower = lower; + this.threshold = threshold; + this.minX = bounds.X; + this.maxX = bounds.Right; + this.isAlphaOnly = isAlphaOnly; + } - ParallelHelper.IterateRows( - workingRect, - configuration, - rows => - { - Rgba32 rgba = default; - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = source.GetPixelRowSpan(y); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Rgba32 rgba = default; + Span row = this.source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); - for (int x = startX; x < endX; x++) - { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + for (int x = this.minX; x < this.maxX; x++) + { + ref TPixel color = ref Unsafe.Add(ref rowRef, x); + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - color = luminance >= threshold ? upper : lower; - } - } - }); + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index 5e9ca2e542..36cc7f6973 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -12,11 +11,11 @@ namespace SixLabors.ImageSharp.Processing.Processors public abstract class CloningImageProcessor : ICloningImageProcessor { /// - public abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + public abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; /// - IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - => this.CreatePixelSpecificCloningProcessor(source, sourceRectangle); + IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => this.CreatePixelSpecificCloningProcessor(configuration, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index 42d2f0e1df..e933978c20 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -2,11 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Linq; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -17,18 +13,19 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public abstract class CloningImageProcessor : ICloningImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The source for the current processor instance. /// The source area to process for the current processor instance. - protected CloningImageProcessor(Image source, Rectangle sourceRectangle) + protected CloningImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) { + this.Configuration = configuration; this.Source = source; this.SourceRectangle = sourceRectangle; - this.Configuration = this.Source.GetConfiguration(); } /// @@ -54,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors Image clone = this.CreateTarget(); this.CheckFrameCount(this.Source, clone); - Configuration configuration = this.Source.GetConfiguration(); + Configuration configuration = this.Configuration; this.BeforeImageApply(clone); for (int i = 0; i < this.Source.Frames.Count; i++) @@ -113,10 +110,10 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - /// Gets the size of the target image. + /// Gets the size of the destination image. /// /// The . - protected abstract Size GetTargetSize(); + protected abstract Size GetDestinationSize(); /// /// This method is called before the process is applied to prepare the processor. @@ -171,18 +168,21 @@ namespace SixLabors.ImageSharp.Processing.Processors private Image CreateTarget() { Image source = this.Source; - Size targetSize = this.GetTargetSize(); - - // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select, ImageFrame>( - x => new ImageFrame( - source.GetConfiguration(), - targetSize.Width, - targetSize.Height, - x.Metadata.DeepClone())); - - // Use the overload to prevent an extra frame being added - return new Image(this.Configuration, source.Metadata.DeepClone(), frames); + Size destinationSize = this.GetDestinationSize(); + + // We will always be creating the clone even for mutate because we may need to resize the canvas. + var destinationFrames = new ImageFrame[source.Frames.Count]; + for (int i = 0; i < destinationFrames.Length; i++) + { + destinationFrames[i] = new ImageFrame( + this.Configuration, + destinationSize.Width, + destinationSize.Height, + source.Frames[i].Metadata.DeepClone()); + } + + // Use the overload to prevent an extra frame being added. + return new Image(this.Configuration, source.Metadata.DeepClone(), destinationFrames); } private void CheckFrameCount(Image a, Image b) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index b7e102deb5..65aa81c608 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -26,27 +25,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public const float DefaultGamma = 3F; - /// - /// The default execution mode used by the parameterless constructor. - /// - public const BokehBlurExecutionMode DefaultExecutionMode = BokehBlurExecutionMode.PreferLowMemoryUsage; - /// /// Initializes a new instance of the class. /// public BokehBlurProcessor() - : this(DefaultRadius, DefaultComponents, DefaultGamma, DefaultExecutionMode) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The execution mode to use when applying the processor. - /// - public BokehBlurProcessor(BokehBlurExecutionMode executionMode) - : this(DefaultRadius, DefaultComponents, DefaultGamma, executionMode) + : this(DefaultRadius, DefaultComponents, DefaultGamma) { } @@ -63,33 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The gamma highlight factor to use to further process the image. /// public BokehBlurProcessor(int radius, int components, float gamma) - : this(radius, components, gamma, DefaultExecutionMode) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - /// - /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. - /// - /// - /// The gamma highlight factor to use to further process the image. - /// - /// - /// The execution mode to use when applying the processor. - /// - public BokehBlurProcessor(int radius, int components, float gamma, BokehBlurExecutionMode executionMode) { Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma)); this.Radius = radius; this.Components = components; this.Gamma = gamma; - this.ExecutionMode = executionMode; } /// @@ -107,16 +69,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public float Gamma { get; } - /// - /// Gets the execution mode to use when applying the effect. - /// - public BokehBlurExecutionMode ExecutionMode { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new BokehBlurProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BokehBlurProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 9339c8fe43..cf97751bef 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -2,19 +2,14 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -24,33 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The pixel format. /// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve. internal class BokehBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - /// - /// The kernel radius. - /// - private readonly int radius; - /// /// The gamma highlight factor to use when applying the effect /// private readonly float gamma; - /// - /// The execution mode to use when applying the effect - /// - private readonly BokehBlurExecutionMode executionMode; - - /// - /// The maximum size of the kernel in either direction - /// - private readonly int kernelSize; - - /// - /// The number of components to use when applying the bokeh blur - /// - private readonly int componentsCount; - /// /// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W) /// @@ -61,49 +36,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// private readonly Complex64[][] kernels; - /// - /// The scaling factor for kernel values - /// - private readonly float kernelsScale; - - /// - /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances - /// - private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); - /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public BokehBlurProcessor(BokehBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - this.radius = definition.Radius; - this.kernelSize = (this.radius * 2) + 1; - this.componentsCount = definition.Components; this.gamma = definition.Gamma; - this.executionMode = definition.ExecutionMode; - // Reuse the initialized values from the cache, if possible - var parameters = new BokehBlurParameters(this.radius, this.componentsCount); - if (Cache.TryGetValue(parameters, out BokehBlurKernelData info)) - { - this.kernelParameters = info.Parameters; - this.kernelsScale = info.Scale; - this.kernels = info.Kernels; - } - else - { - // Initialize the complex kernels and parameters with the current arguments - (this.kernelParameters, this.kernelsScale) = this.GetParameters(); - this.kernels = this.CreateComplexKernels(); - this.NormalizeKernels(); + // Get the bokeh blur data + BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData( + definition.Radius, + (definition.Radius * 2) + 1, + definition.Components); - // Store them in the cache for future use - Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels)); - } + this.kernelParameters = data.Parameters; + this.kernels = data.Kernels; } /// @@ -116,195 +68,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public IReadOnlyList KernelParameters => this.kernelParameters; - /// - /// Gets the kernel scales to adjust the component values in each kernel - /// - private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; - - /// - /// Gets the available bokeh blur kernel parameters - /// - private static IReadOnlyList KernelComponents { get; } = new[] - { - // 1 component - new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, - - // 2 components - new[] - { - new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), - new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) - }, - - // 3 components - new[] - { - new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f), - new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f), - new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f) - }, - - // 4 components - new[] - { - new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f), - new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f), - new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f), - new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f) - }, - - // 5 components - new[] - { - new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f), - new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f), - new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f), - new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f), - new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f) - }, - - // 6 components - new[] - { - new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f), - new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f), - new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f), - new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f), - new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f), - new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f) - } - }; - - /// - /// Gets the kernel parameters and scaling factor for the current count value in the current instance - /// - private (Vector4[] Parameters, float Scale) GetParameters() - { - // Prepare the kernel components - int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelComponents.Count)); - return (KernelComponents[index], KernelScales[index]); - } - - /// - /// Creates the collection of complex 1D kernels with the specified parameters - /// - private Complex64[][] CreateComplexKernels() - { - var kernels = new Complex64[this.kernelParameters.Length][]; - ref Vector4 baseRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - for (int i = 0; i < this.kernelParameters.Length; i++) - { - ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i); - kernels[i] = this.CreateComplex1DKernel(paramsRef.X, paramsRef.Y); - } - - return kernels; - } - - /// - /// Creates a complex 1D kernel with the specified parameters - /// - /// The exponential parameter for each complex component - /// The angle component for each complex component - private Complex64[] CreateComplex1DKernel(float a, float b) - { - var kernel = new Complex64[this.kernelSize]; - ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); - int r = this.radius, n = -r; - - for (int i = 0; i < this.kernelSize; i++, n++) - { - // Incrementally compute the range values - float value = n * this.kernelsScale * (1f / r); - value *= value; - - // Fill in the complex kernel values - Unsafe.Add(ref baseRef, i) = new Complex64( - MathF.Exp(-a * value) * MathF.Cos(b * value), - MathF.Exp(-a * value) * MathF.Sin(b * value)); - } - - return kernel; - } - - /// - /// Normalizes the kernels with respect to A * real + B * imaginary - /// - private void NormalizeKernels() - { - // Calculate the complex weighted sum - float total = 0; - Span kernelsSpan = this.kernels.AsSpan(); - ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan); - ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - - for (int i = 0; i < this.kernelParameters.Length; i++) - { - ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i); - int length = kernelRef.Length; - ref Complex64 valueRef = ref kernelRef[0]; - ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i); - - for (int j = 0; j < length; j++) - { - for (int k = 0; k < length; k++) - { - ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j); - ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k); - total += - (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary))) - + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real))); - } - } - } - - // Normalize the kernels - float scalar = 1f / MathF.Sqrt(total); - for (int i = 0; i < kernelsSpan.Length; i++) - { - ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i); - int length = kernelsRef.Length; - ref Complex64 valueRef = ref kernelsRef[0]; - - for (int j = 0; j < length; j++) - { - Unsafe.Add(ref valueRef, j) *= scalar; - } - } - } - /// protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration); + var gammaOperation = new ApplyGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.SourceRectangle, + in gammaOperation); // Create a 0-filled buffer to use to store the result of the component convolutions - using (Buffer2D processing = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean)) - { - if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage) - { - // Memory usage priority: allocate a shared buffer and execute the second convolution in sequential mode - using (Buffer2D buffer = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height + this.radius)) - using (Buffer2D firstPassBuffer = buffer.Slice(this.radius, source.Height)) - using (Buffer2D secondPassBuffer = buffer.Slice(0, source.Height)) - { - this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processing, firstPassBuffer, secondPassBuffer); - } - } - else - { - // Performance priority: allocate two independent buffers and execute both convolutions in parallel mode - using (Buffer2D firstPassValues = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - using (Buffer2D secondPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processing, firstPassValues, secondPassBuffer); - } - } + using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); - // Apply the inverse gamma exposure pass, and write the final pixel data - this.ApplyInverseGammaExposure(source.PixelBuffer, processing, this.SourceRectangle, this.Configuration); - } + // Perform the 1D convolutions on all the kernel components and accumulate the results + this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); + + float inverseGamma = 1 / this.gamma; + + // Apply the inverse gamma exposure pass, and write the final pixel data + var operation = new ApplyInverseGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.SourceRectangle, + in operation); } /// @@ -314,275 +101,217 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The structure that specifies the portion of the image object to draw. /// The configuration. /// The buffer with the raw pixel data to use to aggregate the results of each convolution. - /// The complex buffer to use for the first 1D convolution pass for each kernel. - /// The complex buffer to use for the second 1D convolution pass for each kernel. private void OnFrameApplyCore( ImageFrame source, Rectangle sourceRectangle, Configuration configuration, - Buffer2D processingBuffer, - Buffer2D firstPassBuffer, - Buffer2D secondPassBuffer) + Buffer2D processingBuffer) { + // Allocate the buffer with the intermediate convolution results + using Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + // Perform two 1D convolutions for each component in the current instance ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); for (int i = 0; i < this.kernels.Length; i++) { // Compute the resulting complex buffer for the current component - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); Complex64[] kernel = Unsafe.Add(ref baseRef, i); - this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration); - this.ApplyConvolution(secondPassBuffer, firstPassBuffer, interest, kernel, configuration); - - // Add the results of the convolution with the current kernel - Vector4 parameters = this.kernelParameters[i]; - this.SumProcessingPartials(processingBuffer, secondPassBuffer, sourceRectangle, configuration, parameters.Z, parameters.W); + Vector4 parameters = Unsafe.Add(ref paramsRef, i); + + // Compute the vertical 1D convolution + var verticalOperation = new ApplyVerticalConvolutionRowOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in verticalOperation); + + // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + var horizontalOperation = new ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in horizontalOperation); } } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the vertical convolution logic for . /// - /// The target values to use to store the results. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The 1D kernel. - /// The - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration) + private readonly struct ApplyVerticalConvolutionRowOperation : IRowOperation { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly Complex64[] kernel; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyVerticalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + Complex64[] kernel) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetValues = targetValues; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); - - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX); - } - } - }); + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); + } + } } /// - /// Applies the process to the specified portion of the specified buffer at the specified location - /// and with the specified size. + /// A implementing the horizontal convolution logic for . /// - /// The target values to use to store the results. - /// The source complex values. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The 1D kernel. - /// The - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration) + private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage) + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyHorizontalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + Complex64[] kernel, + float z, + float w) { - configuration = configuration.Clone(); - configuration.MaxDegreeOfParallelism = 1; + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetValues = targetValues; + this.sourceValues = sourceValues; + this.kernel = kernel; + this.z = z; + this.w = w; } - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); - - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX); - } - } - }); + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); + } + } } /// - /// Applies the gamma correction/highlight to the input pixel buffer. + /// A implementing the gamma exposure logic for . /// - /// The target pixel buffer to adjust. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyGammaExposure( - Buffer2D targetPixels, - Rectangle sourceRectangle, - Configuration configuration) + private readonly struct ApplyGammaExposureRowOperation : IRowOperation { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float exp = this.gamma; - - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, exp); - v.Y = MathF.Pow(v.Y, exp); - v.Z = MathF.Pow(v.Z, exp); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); - } - }); - } + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + private readonly float gamma; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGammaExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Configuration configuration, + float gamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.configuration = configuration; + this.gamma = gamma; + } - /// - /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values. - /// - /// The target pixels to apply the process to. - /// The source values. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyInverseGammaExposure( - Buffer2D targetPixels, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Configuration configuration) - { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float expGamma = 1 / this.gamma; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => - { - Vector4 low = Vector4.Zero; - var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, expGamma); - v.Y = MathF.Pow(clamp.Y, expGamma); - v.Z = MathF.Pow(clamp.Z, expGamma); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply); - } - }); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the inverse gamma exposure logic for . /// - /// The target instance to use to store the results. - /// The source complex pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - /// The weight factor for the real component of the complex pixel values. - /// The weight factor for the imaginary component of the complex pixel values. - private void SumProcessingPartials( - Buffer2D targetValues, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Configuration configuration, - float z, - float w) + private readonly struct ApplyInverseGammaExposureRowOperation : IRowOperation { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourceValues; + private readonly Configuration configuration; + private readonly float inverseGamma; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyInverseGammaExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourceValues, + Configuration configuration, + float inverseGamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourceValues = sourceValues; + this.configuration = configuration; + this.inverseGamma = inverseGamma; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Vector4 low = Vector4.Zero; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + + for (int x = 0; x < this.bounds.Width; x++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); - ref Vector4 baseTargetRef = ref MemoryMarshal.GetReference(targetRowSpan); - ref ComplexVector4 baseSourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < width; x++) - { - Unsafe.Add(ref baseTargetRef, x) += Unsafe.Add(ref baseSourceRef, x).WeightedSum(z, w); - } - } - }); + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4Utilities.FastClamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index a5368c4639..92f7ab02d2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Defines a box blur processor of a given Radius. + /// Defines a box blur processor of a given radius. /// public sealed class BoxBlurProcessor : IImageProcessor { @@ -41,10 +40,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public int Radius { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new BoxBlurProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BoxBlurProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs index 77110e642d..fc80905ee4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -12,20 +10,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class BoxBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - private readonly BoxBlurProcessor definition; - /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public BoxBlurProcessor(BoxBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public BoxBlurProcessor(Configuration configuration, BoxBlurProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - this.definition = definition; int kernelSize = (definition.Radius * 2) + 1; this.KernelX = CreateBoxKernel(kernelSize); this.KernelY = this.KernelX.Transpose(); @@ -44,10 +40,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index b38d87cd4f..f7439879e4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -3,13 +3,11 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -18,23 +16,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class Convolution2DProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The horizontal gradient operator. /// The vertical gradient operator. /// Whether the convolution filter is applied to alpha as well as the color channels. /// The source for the current processor instance. /// The source area to process for the current processor instance. public Convolution2DProcessor( + Configuration configuration, in DenseMatrix kernelX, in DenseMatrix kernelY, bool preserveAlpha, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + : base(configuration, source, sourceRectangle) { Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; @@ -60,79 +60,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix matrixY = this.KernelY; - DenseMatrix matrixX = this.KernelX; - bool preserveAlpha = this.PreserveAlpha; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - int maxY = endY - 1; - int maxX = endX - 1; + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) - { - source.CopyTo(targetPixels); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernelY; + private readonly DenseMatrix kernelX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernelY, + DenseMatrix kernelX, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernelY = kernelY; + this.kernelX = kernelX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D3( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D4( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index a523f7c227..4bbb15cbad 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -3,12 +3,11 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -17,23 +16,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class Convolution2PassProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The horizontal gradient operator. /// The vertical gradient operator. /// Whether the convolution filter is applied to alpha as well as the color channels. /// The source for the current processor instance. /// The source area to process for the current processor instance. public Convolution2PassProcessor( + Configuration configuration, in DenseMatrix kernelX, in DenseMatrix kernelY, bool preserveAlpha, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + : base(configuration, source, sourceRectangle) { this.KernelX = kernelX; this.KernelY = kernelY; @@ -58,95 +59,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, this.Configuration); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, this.Configuration); - } + using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // Horizontal convolution + var horizontalOperation = new RowOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in horizontalOperation); + + // Vertical convolution + var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in verticalOperation); } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the convolution logic for . /// - /// The target pixels to apply the process to. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The kernel operator. - /// The - private void ApplyConvolution( - Buffer2D targetPixels, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - in DenseMatrix kernel, - Configuration configuration) + private readonly struct RowOperation : IRowOperation { - DenseMatrix matrix = kernel; - bool preserveAlpha = this.PreserveAlpha; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - configuration, - (rows, vectorBuffer) => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; + + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve3( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve4( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); - } - }); + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs index 661ab523db..34b085fc6e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -3,8 +3,6 @@ using System; -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { internal static class ConvolutionProcessorHelpers diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 5bdec738d7..8201b8e239 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -3,12 +3,11 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -17,21 +16,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class ConvolutionProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The 2d gradient operator. /// Whether the convolution filter is applied to alpha as well as the color channels. /// The source for the current processor instance. /// The source area to process for the current processor instance. public ConvolutionProcessor( + Configuration configuration, in DenseMatrix kernelXY, bool preserveAlpha, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + : base(configuration, source, sourceRectangle) { this.KernelXY = kernelXY; this.PreserveAlpha = preserveAlpha; @@ -50,76 +51,96 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix matrix = this.KernelXY; - bool preserveAlpha = this.PreserveAlpha; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - int maxY = endY - 1; - int maxX = endX - 1; + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - source.CopyTo(targetPixels); + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve3( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve4( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 8358abe7df..8bb60286a7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -13,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class EdgeDetector2DProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The horizontal gradient operator. /// The vertical gradient operator. /// Whether to convert the image to grayscale before performing edge detection. /// The source for the current processor instance. /// The source area to process for the current processor instance. internal EdgeDetector2DProcessor( + Configuration configuration, in DenseMatrix kernelX, in DenseMatrix kernelY, bool grayscale, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + : base(configuration, source, sourceRectangle) { Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; @@ -52,9 +52,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void BeforeImageApply() { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) + { + opaque.Execute(); + } + if (this.Grayscale) { - new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle); + new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } base.BeforeImageApply(); @@ -63,10 +68,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2DProcessor(this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index dc9974c616..1b07589b51 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -1,17 +1,13 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -20,17 +16,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class EdgeDetectorCompassProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// Gets the kernels to use. /// Whether to convert the image to grayscale before performing edge detection. /// The source for the current processor instance. /// The source area to process for the current processor instance. - internal EdgeDetectorCompassProcessor(CompassKernels kernels, bool grayscale, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + internal EdgeDetectorCompassProcessor(Configuration configuration, CompassKernels kernels, bool grayscale, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.Grayscale = grayscale; this.Kernels = kernels; @@ -43,9 +40,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void BeforeImageApply() { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) + { + opaque.Execute(); + } + if (this.Grayscale) { - new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle); + new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } base.BeforeImageApply(); @@ -56,89 +58,77 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { DenseMatrix[] kernels = this.Kernels.Flatten(); - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + // We need a clean copy for each pass to start from + using ImageFrame cleanCopy = source.Clone(); - // we need a clean copy for each pass to start from - using (ImageFrame cleanCopy = source.Clone()) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, interest)) { - using (var processor = new ConvolutionProcessor(kernels[0], true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + processor.Apply(source); + } - if (kernels.Length == 1) - { - return; - } + if (kernels.Length == 1) + { + return; + } - int shiftY = startY; - int shiftX = startX; + // Additional runs + for (int i = 1; i < kernels.Length; i++) + { + using ImageFrame pass = cleanCopy.Clone(); - // Reset offset if necessary. - if (minX > 0) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest)) { - shiftX = 0; + processor.Apply(pass); } - if (minY > 0) - { - shiftY = 0; - } + var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Buffer2D targetPixels; + private readonly Buffer2D passPixels; + private readonly int minX; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Buffer2D targetPixels, + Buffer2D passPixels, + Rectangle bounds) + { + this.targetPixels = targetPixels; + this.passPixels = passPixels; + this.minX = bounds.X; + this.maxX = bounds.Right; + } - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); - // Additional runs. - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 1; i < kernels.Length; i++) + for (int x = this.minX; x < this.maxX; x++) { - using (ImageFrame pass = cleanCopy.Clone()) - { - using (var processor = new ConvolutionProcessor(kernels[i], true, this.Source, this.SourceRectangle)) - { - processor.Apply(pass); - } - - Buffer2D passPixels = pass.PixelBuffer; - Buffer2D targetPixels = source.PixelBuffer; - - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - shiftY; - - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; - - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); - - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); - - currentTargetPixel.FromVector4(pixelValue); - } - } - }); - } + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); + + var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); + + currentTargetPixel.FromVector4(pixelValue); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index 24b95da696..472547765e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public bool Grayscale { get; } /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index 5246dc3b72..8ca548d975 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -13,17 +11,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class EdgeDetectorProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The 2d gradient operator. /// Whether to convert the image to grayscale before performing edge detection. /// The source for the current processor instance. /// The target area to process for the current processor instance. - public EdgeDetectorProcessor(in DenseMatrix kernelXY, bool grayscale, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public EdgeDetectorProcessor( + Configuration configuration, + in DenseMatrix kernelXY, + bool grayscale, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.KernelXY = kernelXY; this.Grayscale = grayscale; @@ -39,9 +43,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void BeforeImageApply() { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) + { + opaque.Execute(); + } + if (this.Grayscale) { - new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle); + new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } base.BeforeImageApply(); @@ -50,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new ConvolutionProcessor(this.KernelXY, true, this.Source, this.SourceRectangle)) + using (var processor = new ConvolutionProcessor(this.Configuration, this.KernelXY, true, this.Source, this.SourceRectangle)) { processor.Apply(source); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index aabc8041d9..d566f6691c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -71,10 +70,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public int Radius { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new GaussianBlurProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GaussianBlurProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index bbf36ea5e8..cb77c8741f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -12,16 +10,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class GaussianBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public GaussianBlurProcessor(GaussianBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public GaussianBlurProcessor( + Configuration configuration, + GaussianBlurProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { int kernelSize = (definition.Radius * 2) + 1; this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); @@ -41,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 0262ec8e44..4854eae687 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -11,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public sealed class GaussianSharpenProcessor : IImageProcessor { - /// + /// /// The default value for . /// public const float DefaultSigma = 3f; @@ -71,10 +70,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public int Radius { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new GaussianSharpenProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs index dab55b2328..24c56363ae 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { @@ -12,16 +10,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class GaussianSharpenProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public GaussianSharpenProcessor(GaussianSharpenProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public GaussianSharpenProcessor( + Configuration configuration, + GaussianSharpenProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { int kernelSize = (definition.Radius * 2) + 1; this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); @@ -41,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs index 2026512617..90ed15aa33 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,14 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetector2DProcessor( + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetector2DProcessor( + configuration, KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY, this.Grayscale, source, sourceRectangle); - } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs index f44de9105b..423fc65917 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { internal abstract class CompassKernels diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs index dd4d023025..50d5bfafe4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs index 882b87075b..58568ce409 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs index 19f2d1161b..8371212fe8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs index e7b7f965b9..f72e95ee8b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs index 381e028d49..cae9ecb5bf 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs index f61220e1ec..8ffd624d21 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs index 699d669ec9..ba60bfdf64 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs index f0662c6672..ec583862f7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs index 113957c839..3dbd54a2c5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs index bbbfc64d92..7207f95c4b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetectorCompassProcessor(new KirschKernels(), this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetectorCompassProcessor(configuration, new KirschKernels(), this.Grayscale, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs index 64f99ebe61..b147a87cc8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetectorProcessor(LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetectorProcessor(configuration, LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs index d1c909a941..663ebf0517 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetectorProcessor(LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetectorProcessor(configuration, LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs index 0eecaefe16..8b0cfc6ff3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetectorProcessor(LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetectorProcessor(configuration, LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs index 4338bcf6b9..561892683a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs @@ -1,10 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters { /// @@ -17,11 +15,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters /// public readonly Vector4[] Parameters; - /// - /// The scaling factor for the kernel values - /// - public readonly float Scale; - /// /// The kernel components to apply the bokeh blur effect /// @@ -31,12 +24,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters /// Initializes a new instance of the struct. /// /// The kernel parameters - /// The kernel scale factor /// The complex kernel components - public BokehBlurKernelData(Vector4[] parameters, float scale, Complex64[][] kernels) + public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels) { this.Parameters = parameters; - this.Scale = scale; this.Kernels = kernels; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs new file mode 100644 index 0000000000..f7828fa9ef --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters +{ + /// + /// Provides parameters to be used in the . + /// + internal static class BokehBlurKernelDataProvider + { + /// + /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances + /// + private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); + + /// + /// Gets the kernel scales to adjust the component values in each kernel + /// + private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; + + /// + /// Gets the available bokeh blur kernel parameters + /// + private static IReadOnlyList KernelComponents { get; } = new[] + { + // 1 component + new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, + + // 2 components + new[] + { + new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), + new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) + }, + + // 3 components + new[] + { + new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f), + new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f), + new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f) + }, + + // 4 components + new[] + { + new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f), + new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f), + new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f), + new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f) + }, + + // 5 components + new[] + { + new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f), + new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f), + new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f), + new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f), + new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f) + }, + + // 6 components + new[] + { + new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f), + new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f), + new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f), + new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f), + new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f), + new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f) + } + }; + + /// + /// Gets the bokeh blur kernel data for the specified parameters. + /// + /// The value representing the size of the area to sample. + /// The size of each kernel to compute. + /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. + /// A instance with the kernel data for the current parameters. + public static BokehBlurKernelData GetBokehBlurKernelData( + int radius, + int kernelSize, + int componentsCount) + { + // Reuse the initialized values from the cache, if possible + var parameters = new BokehBlurParameters(radius, componentsCount); + if (!Cache.TryGetValue(parameters, out BokehBlurKernelData info)) + { + // Initialize the complex kernels and parameters with the current arguments + (Vector4[] kernelParameters, float kernelsScale) = GetParameters(componentsCount); + Complex64[][] kernels = CreateComplexKernels(kernelParameters, radius, kernelSize, kernelsScale); + NormalizeKernels(kernels, kernelParameters); + + // Store them in the cache for future use + info = new BokehBlurKernelData(kernelParameters, kernels); + Cache.TryAdd(parameters, info); + } + + return info; + } + + /// + /// Gets the kernel parameters and scaling factor for the current count value in the current instance + /// + private static (Vector4[] Parameters, float Scale) GetParameters(int componentsCount) + { + // Prepare the kernel components + int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Count)); + + return (KernelComponents[index], KernelScales[index]); + } + + /// + /// Creates the collection of complex 1D kernels with the specified parameters + /// + /// The parameters to use to normalize the kernels + /// The value representing the size of the area to sample. + /// The size of each kernel to compute. + /// The scale factor for each kernel. + private static Complex64[][] CreateComplexKernels( + Vector4[] kernelParameters, + int radius, + int kernelSize, + float kernelsScale) + { + var kernels = new Complex64[kernelParameters.Length][]; + ref Vector4 baseRef = ref MemoryMarshal.GetReference(kernelParameters.AsSpan()); + for (int i = 0; i < kernelParameters.Length; i++) + { + ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i); + kernels[i] = CreateComplex1DKernel(radius, kernelSize, kernelsScale, paramsRef.X, paramsRef.Y); + } + + return kernels; + } + + /// + /// Creates a complex 1D kernel with the specified parameters + /// + /// The value representing the size of the area to sample. + /// The size of each kernel to compute. + /// The scale factor for each kernel. + /// The exponential parameter for each complex component + /// The angle component for each complex component + private static Complex64[] CreateComplex1DKernel( + int radius, + int kernelSize, + float kernelsScale, + float a, + float b) + { + var kernel = new Complex64[kernelSize]; + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); + int r = radius, n = -r; + + for (int i = 0; i < kernelSize; i++, n++) + { + // Incrementally compute the range values + float value = n * kernelsScale * (1f / r); + value *= value; + + // Fill in the complex kernel values + Unsafe.Add(ref baseRef, i) = new Complex64( + MathF.Exp(-a * value) * MathF.Cos(b * value), + MathF.Exp(-a * value) * MathF.Sin(b * value)); + } + + return kernel; + } + + /// + /// Normalizes the kernels with respect to A * real + B * imaginary + /// + /// The current convolution kernels to normalize + /// The parameters to use to normalize the kernels + private static void NormalizeKernels(Complex64[][] kernels, Vector4[] kernelParameters) + { + // Calculate the complex weighted sum + float total = 0; + Span kernelsSpan = kernels.AsSpan(); + ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan); + ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(kernelParameters.AsSpan()); + + for (int i = 0; i < kernelParameters.Length; i++) + { + ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i); + int length = kernelRef.Length; + ref Complex64 valueRef = ref kernelRef[0]; + ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i); + + for (int j = 0; j < length; j++) + { + for (int k = 0; k < length; k++) + { + ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j); + ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k); + total += + (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary))) + + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real))); + } + } + } + + // Normalize the kernels + float scalar = 1f / MathF.Sqrt(total); + for (int i = 0; i < kernelsSpan.Length; i++) + { + ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i); + int length = kernelsRef.Length; + ref Complex64 valueRef = ref kernelsRef[0]; + + for (int j = 0; j < length; j++) + { + Unsafe.Add(ref valueRef, j) *= scalar; + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs index 242e3f7b97..7fc54ff967 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetector2DProcessor(PrewittKernels.PrewittX, PrewittKernels.PrewittY, this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetector2DProcessor( + configuration, + PrewittKernels.PrewittX, + PrewittKernels.PrewittY, + this.Grayscale, + source, + sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs index 481a990ff9..74d5094f53 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,14 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetector2DProcessor( + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetector2DProcessor( + configuration, RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY, this.Grayscale, source, sourceRectangle); - } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs index 324ed31545..18ac906140 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetectorCompassProcessor(new RobinsonKernels(), this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetectorCompassProcessor(configuration, new RobinsonKernels(), this.Grayscale, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs index 6a4bf6afd5..24248204b6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetector2DProcessor(ScharrKernels.ScharrX, ScharrKernels.ScharrY, this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetector2DProcessor( + configuration, + ScharrKernels.ScharrX, + ScharrKernels.ScharrY, + this.Grayscale, + source, + sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs index 96ed3bcea5..1ab56d1203 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// @@ -21,9 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new EdgeDetector2DProcessor(SobelKernels.SobelX, SobelKernels.SobelY, this.Grayscale, source, sourceRectangle); - } + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new EdgeDetector2DProcessor( + configuration, + SobelKernels.SobelX, + SobelKernels.SobelY, + this.Grayscale, + source, + sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs deleted file mode 100644 index 0461d179ff..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. - /// - /// - public sealed class AtkinsonDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix AtkinsonMatrix = - new float[,] - { - { 0, 0, 1, 1 }, - { 1, 1, 1, 0 }, - { 0, 1, 0, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public AtkinsonDiffuser() - : base(AtkinsonMatrix, 8) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs deleted file mode 100644 index b7fdfbfe5f..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 2x2 Bayer dithering matrix. - /// - public sealed class BayerDither2x2 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither2x2() - : base(2) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs deleted file mode 100644 index 4f6d5dd077..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 4x4 Bayer dithering matrix. - /// - public sealed class BayerDither4x4 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither4x4() - : base(4) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs deleted file mode 100644 index 8d0c23aa30..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 8x8 Bayer dithering matrix. - /// - public sealed class BayerDither8x8 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither8x8() - : base(8) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs deleted file mode 100644 index 23d4321e96..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Burks image dithering algorithm. - /// - /// - public sealed class BurksDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix BurksMatrix = - new float[,] - { - { 0, 0, 0, 8, 4 }, - { 2, 4, 8, 4, 2 } - }; - - /// - /// Initializes a new instance of the class. - /// - public BurksDiffuser() - : base(BurksMatrix, 32) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs new file mode 100644 index 0000000000..d39237a2cc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs @@ -0,0 +1,188 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An error diffusion dithering implementation. + /// + public readonly partial struct ErrorDither + { + /// + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + public static ErrorDither Atkinson = CreateAtkinson(); + + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + public static ErrorDither Burkes = CreateBurks(); + + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + public static ErrorDither FloydSteinberg = CreateFloydSteinberg(); + + /// + /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. + /// + public static ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); + + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + public static ErrorDither Sierra2 = CreateSierra2(); + + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + public static ErrorDither Sierra3 = CreateSierra3(); + + /// + /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. + /// + public static ErrorDither SierraLite = CreateSierraLite(); + + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public static ErrorDither StevensonArce = CreateStevensonArce(); + + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + public static ErrorDither Stucki = CreateStucki(); + + private static ErrorDither CreateAtkinson() + { + const float Divisor = 8F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 1 / Divisor, 1 / Divisor }, + { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 }, + { 0, 1 / Divisor, 0, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateBurks() + { + const float Divisor = 32F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateFloydSteinberg() + { + const float Divisor = 16F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 7 / Divisor }, + { 3 / Divisor, 5 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateJarvisJudiceNinke() + { + const float Divisor = 48F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 7 / Divisor, 5 / Divisor }, + { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor }, + { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierra2() + { + const float Divisor = 16F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 4 / Divisor, 3 / Divisor }, + { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierra3() + { + const float Divisor = 32F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 5 / Divisor, 3 / Divisor }, + { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor }, + { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierraLite() + { + const float Divisor = 4F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 2 / Divisor }, + { 1 / Divisor, 1 / Divisor, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateStevensonArce() + { + const float Divisor = 200F; + const int Offset = 3; + + var matrix = new float[,] + { + { 0, 0, 0, 0, 0, 32 / Divisor, 0 }, + { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor }, + { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 }, + { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateStucki() + { + const float Divisor = 42F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }, + { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs deleted file mode 100644 index 1c8156bf51..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// The base class for performing error diffusion based dithering. - /// - public abstract class ErrorDiffuser : IErrorDiffuser - { - /// - /// The vector to perform division. - /// - private readonly Vector4 divisorVector; - - /// - /// The matrix width. - /// - private readonly int matrixHeight; - - /// - /// The matrix height. - /// - private readonly int matrixWidth; - - /// - /// The offset at which to start the dithering operation. - /// - private readonly int startingOffset; - - /// - /// The diffusion matrix. - /// - private readonly DenseMatrix matrix; - - /// - /// Initializes a new instance of the class. - /// - /// The dithering matrix. - /// The divisor. - internal ErrorDiffuser(in DenseMatrix matrix, byte divisor) - { - Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); - - this.matrix = matrix; - this.matrixWidth = this.matrix.Columns; - this.matrixHeight = this.matrix.Rows; - this.divisorVector = new Vector4(divisor); - - this.startingOffset = 0; - for (int i = 0; i < this.matrixWidth; i++) - { - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (matrix[0, i] != 0) - { - this.startingOffset = (byte)(i - 1); - break; - } - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) - where TPixel : struct, IPixel - { - image[x, y] = transformed; - - // Equal? Break out as there's nothing to pass. - if (source.Equals(transformed)) - { - return; - } - - // Calculate the error - Vector4 error = source.ToVector4() - transformed.ToVector4(); - this.DoDither(image, x, y, minX, minY, maxX, maxY, error); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void DoDither(ImageFrame image, int x, int y, int minX, int minY, int maxX, int maxY, Vector4 error) - where TPixel : struct, IPixel - { - // Loop through and distribute the error amongst neighboring pixels. - for (int row = 0; row < this.matrixHeight; row++) - { - int matrixY = y + row; - if (matrixY > minY && matrixY < maxY) - { - Span rowSpan = image.GetPixelRowSpan(matrixY); - - for (int col = 0; col < this.matrixWidth; col++) - { - int matrixX = x + (col - this.startingOffset); - - if (matrixX > minX && matrixX < maxX) - { - float coefficient = this.matrix[row, col]; - - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (coefficient == 0) - { - continue; - } - - ref TPixel pixel = ref rowSpan[matrixX]; - var offsetColor = pixel.ToVector4(); - - Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor; - pixel.FromVector4(result); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs deleted file mode 100644 index 4e45130cc3..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Defines a dither operation using error diffusion. - /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. - /// - public sealed class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) - : this(diffuser, .5F) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold) - : this(diffuser, threshold, Color.WebSafePalette) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - /// The palette to select substitute colors from. - public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, ReadOnlyMemory palette) - : base(palette) - { - Guard.NotNull(diffuser, nameof(diffuser)); - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - - this.Diffuser = diffuser; - this.Threshold = threshold; - } - - /// - /// Gets the error diffuser. - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the threshold value. - /// - public float Threshold { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new ErrorDiffusionPaletteProcessor(this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs deleted file mode 100644 index 557a31c336..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ErrorDiffusionPaletteProcessor(ErrorDiffusionPaletteProcessor definition, Image source, Rectangle sourceRectangle) - : base(definition, source, sourceRectangle) - { - } - - private new ErrorDiffusionPaletteProcessor Definition => (ErrorDiffusionPaletteProcessor)base.Definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F); - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - PixelPair pair = this.GetClosestPixelPair(ref sourcePixel); - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - pair = this.GetClosestPixelPair(ref sourcePixel); - - // No error to spread, exact match. - if (sourcePixel.Equals(pair.First)) - { - continue; - } - - sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; - this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs new file mode 100644 index 0000000000..7d30bada6e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -0,0 +1,215 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An error diffusion dithering implementation. + /// + /// + public readonly partial struct ErrorDither : IDither, IEquatable, IEquatable + { + private readonly int offset; + private readonly DenseMatrix matrix; + + /// + /// Initializes a new instance of the struct. + /// + /// The diffusion matrix. + /// The starting offset within the matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public ErrorDither(in DenseMatrix matrix, int offset) + { + this.matrix = matrix; + this.offset = offset; + } + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(IDither left, ErrorDither right) + => right == left; + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(IDither left, ErrorDither right) + => !(right == left); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(ErrorDither left, IDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(ErrorDither left, IDither right) + => !(left == right); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(ErrorDither left, ErrorDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(ErrorDither left, ErrorDither right) + => !(left == right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + int offsetY = bounds.Top; + int offsetX = bounds.Left; + float scale = quantizer.Options.DitherScale; + + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = bounds.Left; x < bounds.Right; x++) + { + TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); + Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); + this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); + } + } + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + float scale = processor.DitherScale; + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + for (int x = bounds.Left; x < bounds.Right; x++) + { + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel); + this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); + sourcePixel = transformed; + } + } + } + + // Internal for AOT + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( + ImageFrame image, + Rectangle bounds, + TPixel source, + TPixel transformed, + int x, + int y, + float scale) + where TPixel : unmanaged, IPixel + { + // Equal? Break out as there's no error to pass. + if (source.Equals(transformed)) + { + return transformed; + } + + // Calculate the error + Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; + + int offset = this.offset; + DenseMatrix matrix = this.matrix; + + // Loop through and distribute the error amongst neighboring pixels. + for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) + { + if (targetY >= bounds.Bottom) + { + continue; + } + + Span rowSpan = image.GetPixelRowSpan(targetY); + + for (int col = 0; col < matrix.Columns; col++) + { + int targetX = x + (col - offset); + if (targetX < bounds.Left || targetX >= bounds.Right) + { + continue; + } + + float coefficient = matrix[row, col]; + if (coefficient == 0) + { + continue; + } + + ref TPixel pixel = ref rowSpan[targetX]; + var result = pixel.ToVector4(); + + result += error * coefficient; + pixel.FromVector4(result); + } + } + + return transformed; + } + + /// + public override bool Equals(object obj) + => obj is ErrorDither dither && this.Equals(dither); + + /// + public bool Equals(ErrorDither other) + => this.offset == other.offset && this.matrix.Equals(other.matrix); + + /// + public bool Equals(IDither other) + => this.Equals((object)other); + + /// + public override int GetHashCode() + => HashCode.Combine(this.offset, this.matrix); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs deleted file mode 100644 index 78a28a693a..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. - /// - /// - public sealed class FloydSteinbergDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix FloydSteinbergMatrix = - new float[,] - { - { 0, 0, 7 }, - { 3, 5, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - public FloydSteinbergDiffuser() - : base(FloydSteinbergMatrix, 16) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs new file mode 100644 index 0000000000..8f9d82537b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Defines the contract for types that apply dithering to images. + /// + public interface IDither + { + /// + /// Transforms the quantized image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. + /// + /// The type of frame quantizer. + /// The pixel format. + /// The frame quantizer. + /// The source image. + /// The destination quantized frame. + /// The region of interest bounds. + void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel; + + /// + /// Transforms the image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. + /// + /// The type of palette dithering processor. + /// The pixel format. + /// The palette dithering processor. + /// The source image. + /// The region of interest bounds. + void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel; + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs deleted file mode 100644 index 5b30c0dc4d..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Encapsulates properties and methods required to perform diffused error dithering on an image. - /// - public interface IErrorDiffuser - { - /// - /// Transforms the image applying the dither matrix. This method alters the input pixels array - /// - /// The image - /// The source pixel - /// The transformed pixel - /// The column index. - /// The row index. - /// The minimum column value. - /// The minimum row value. - /// The maximum column value. - /// The maximum row value. - /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) - where TPixel : struct, IPixel; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs deleted file mode 100644 index 571929b99d..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Encapsulates properties and methods required to perform ordered dithering on an image. - /// - public interface IOrderedDither - { - /// - /// Transforms the image applying the dither matrix. This method alters the input pixels array - /// - /// The image - /// The source pixel - /// The color to apply to the pixels above the threshold. - /// The color to apply to the pixels below the threshold. - /// The threshold to split the image. Must be between 0 and 1. - /// The column index. - /// The row index. - /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) - where TPixel : struct, IPixel; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs new file mode 100644 index 0000000000..a8e08fa3fa --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Implements an algorithm to alter the pixels of an image via palette dithering. + /// + /// The pixel format. + public interface IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + /// + /// Gets the configuration instance to use when performing operations. + /// + Configuration Configuration { get; } + + /// + /// Gets the dithering palette. + /// + ReadOnlyMemory Palette { get; } + + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + float DitherScale { get; } + + /// + /// Returns the color from the dithering palette corresponding to the given color. + /// + /// The color to match. + /// The match. + TPixel GetPaletteColor(TPixel color); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs deleted file mode 100644 index 64c8610833..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. - /// - /// - public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix JarvisJudiceNinkeMatrix = - new float[,] - { - { 0, 0, 0, 7, 5 }, - { 3, 5, 7, 5, 3 }, - { 1, 3, 5, 3, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - public JarvisJudiceNinkeDiffuser() - : base(JarvisJudiceNinkeMatrix, 48) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs new file mode 100644 index 0000000000..f6026a64f7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An ordered dithering matrix with equal sides of arbitrary length + /// + public readonly partial struct OrderedDither + { + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public static OrderedDither Bayer2x2 = new OrderedDither(2); + + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public static OrderedDither Bayer4x4 = new OrderedDither(4); + + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public static OrderedDither Bayer8x8 = new OrderedDither(8); + + /// + /// Applies order dithering using the 3x3 ordered dithering matrix. + /// + public static OrderedDither Ordered3x3 = new OrderedDither(3); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 174732f802..6862cff000 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -1,50 +1,282 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// /// An ordered dithering matrix with equal sides of arbitrary length /// - public class OrderedDither : IOrderedDither + public readonly partial struct OrderedDither : IDither, IEquatable, IEquatable { - private readonly DenseMatrix thresholdMatrix; + private readonly DenseMatrix thresholdMatrix; private readonly int modulusX; private readonly int modulusY; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The length of the matrix sides + [MethodImpl(InliningOptions.ShortMethod)] public OrderedDither(uint length) { DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); + + // Create a new matrix to run against, that pre-thresholds the values. + // We don't want to adjust the original matrix generation code as that + // creates known, easy to test values. + // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm + var thresholdMatrix = new DenseMatrix((int)length); + float m2 = length * length; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) + { + thresholdMatrix[y, x] = ((ditherMatrix[y, x] + 1) / m2) - .5F; + } + } + this.modulusX = ditherMatrix.Columns; this.modulusY = ditherMatrix.Rows; + this.thresholdMatrix = thresholdMatrix; + } + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(IDither left, OrderedDither right) + => right == left; + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(IDither left, OrderedDither right) + => !(right == left); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(OrderedDither left, IDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(OrderedDither left, IDither right) + => !(left == right); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(OrderedDither left, OrderedDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(OrderedDither left, OrderedDither right) + => !(left == right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + var ditherOperation = new QuantizeDitherRowOperation( + ref quantizer, + in Unsafe.AsRef(this), + source, + destination, + bounds); + + ParallelRowIterator.IterateRows( + quantizer.Configuration, + bounds, + in ditherOperation); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + var ditherOperation = new PaletteDitherRowOperation( + in processor, + in Unsafe.AsRef(this), + source, + bounds); - // Adjust the matrix range for 0-255 - // TODO: It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2 - // https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg - int multiplier = 256 / ditherMatrix.Count; - for (int y = 0; y < ditherMatrix.Rows; y++) + ParallelRowIterator.IterateRows( + processor.Configuration, + bounds, + in ditherOperation); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( + TPixel source, + int x, + int y, + int bitDepth, + float scale) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + source.ToRgba32(ref rgba); + Rgba32 attempt; + + // Spread assumes an even colorspace distribution and precision. + // Calculated as 0-255/component count. 256 / bitDepth + // https://bisqwit.iki.fi/story/howto/dither/jy/ + // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm + int spread = 256 / bitDepth; + float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; + + attempt.R = (byte)(rgba.R + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.G = (byte)(rgba.G + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.B = (byte)(rgba.B + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.A = (byte)(rgba.A + factor).Clamp(byte.MinValue, byte.MaxValue); + + TPixel result = default; + result.FromRgba32(attempt); + + return result; + } + + /// + public override bool Equals(object obj) + => obj is OrderedDither dither && this.Equals(dither); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(OrderedDither other) + => this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY; + + /// + public bool Equals(IDither other) + => this.Equals((object)other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); + + private readonly struct QuantizeDitherRowOperation : IRowOperation + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly IndexedImageFrame destination; + private readonly Rectangle bounds; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizeDitherRowOperation( + ref TFrameQuantizer quantizer, + in OrderedDither dither, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) { - for (int x = 0; x < ditherMatrix.Columns; x++) + this.quantizer = quantizer; + this.dither = dither; + this.source = source; + this.destination = destination; + this.bounds = bounds; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Length); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + float scale = this.quantizer.Options.DitherScale; + + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); + Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); } } - - this.thresholdMatrix = ditherMatrix; } - /// - public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) - where TPixel : struct, IPixel + private readonly struct PaletteDitherRowOperation : IRowOperation + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel { - image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; + private readonly TPaletteDitherImageProcessor processor; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly Rectangle bounds; + private readonly float scale; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteDitherRowOperation( + in TPaletteDitherImageProcessor processor, + in OrderedDither dither, + ImageFrame source, + Rectangle bounds) + { + this.processor = processor; + this.dither = dither; + this.source = source; + this.bounds = bounds; + this.scale = processor.DitherScale; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); + } + } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs deleted file mode 100644 index 93bce0578a..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 3x3 dithering matrix. - /// - public sealed class OrderedDither3x3 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public OrderedDither3x3() - : base(3) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs index 4b93c42590..48aaa22d65 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { @@ -21,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { // Calculate the the logarithm of length to the base 2 uint exponent = 0; - uint bayerLength = 0; + uint bayerLength; do { exponent++; @@ -91,4 +90,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return result; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs deleted file mode 100644 index 87bb3e5171..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Defines a dithering operation that dithers an image using error diffusion. - /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. - /// - public sealed class OrderedDitherPaletteProcessor : PaletteDitherProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - public OrderedDitherPaletteProcessor(IOrderedDither dither) - : this(dither, Color.WebSafePalette) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The palette to select substitute colors from. - public OrderedDitherPaletteProcessor(IOrderedDither dither, ReadOnlyMemory palette) - : base(palette) => this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); - - /// - /// Gets the ditherer. - /// - public IOrderedDither Dither { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new OrderedDitherPaletteProcessor(this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs deleted file mode 100644 index 08eaec503d..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class OrderedDitherPaletteProcessor : PaletteDitherProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public OrderedDitherPaletteProcessor(OrderedDitherPaletteProcessor definition, Image source, Rectangle sourceRectangle) - : base(definition, source, sourceRectangle) - { - } - - private new OrderedDitherPaletteProcessor Definition => (OrderedDitherPaletteProcessor)base.Definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - PixelPair pair = this.GetClosestPixelPair(ref sourcePixel); - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - pair = this.GetClosestPixelPair(ref sourcePixel); - - // No error to spread, exact match. - if (sourcePixel.Equals(pair.First)) - { - continue; - } - - sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - this.Definition.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs index 0de964b526..5ce7ccec04 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs @@ -2,33 +2,78 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// The base class for dither and diffusion processors that consume a palette. + /// Allows the consumption a palette to dither an image. /// - public abstract class PaletteDitherProcessor : IImageProcessor + public sealed class PaletteDitherProcessor : IImageProcessor { /// /// Initializes a new instance of the class. /// + /// The ordered ditherer. + public PaletteDitherProcessor(IDither dither) + : this(dither, QuantizerConstants.MaxDitherScale) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + public PaletteDitherProcessor(IDither dither, float ditherScale) + : this(dither, ditherScale, Color.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The dithering algorithm. /// The palette to select substitute colors from. - protected PaletteDitherProcessor(ReadOnlyMemory palette) + public PaletteDitherProcessor(IDither dither, ReadOnlyMemory palette) + : this(dither, QuantizerConstants.MaxDitherScale, palette) { + } + + /// + /// Initializes a new instance of the class. + /// + /// The dithering algorithm. + /// The dithering scale used to adjust the amount of dither. + /// The palette to select substitute colors from. + public PaletteDitherProcessor(IDither dither, float ditherScale, ReadOnlyMemory palette) + { + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + Guard.NotNull(dither, nameof(dither)); + this.Dither = dither; + this.DitherScale = ditherScale.Clamp(QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); this.Palette = palette; } + /// + /// Gets the dithering algorithm to apply to the output image. + /// + public IDither Dither { get; } + + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + public float DitherScale { get; } + /// /// Gets the palette to select substitute colors from. /// public ReadOnlyMemory Palette { get; } /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new PaletteDitherProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 9f817267fe..e0dd4eae18 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -2,120 +2,104 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Numerics; +using System.Buffers; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// The base class for dither and diffusion processors that consume a palette. + /// Allows the consumption a palette to dither an image. /// /// The pixel format. - internal abstract class PaletteDitherProcessor : ImageProcessor - where TPixel : struct, IPixel + internal sealed class PaletteDitherProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { - private readonly Dictionary> cache = new Dictionary>(); - - private TPixel[] palette; - - /// - /// The vector representation of the image palette. - /// - private Vector4[] paletteVector; + private readonly DitherProcessor ditherProcessor; + private readonly IDither dither; + private IMemoryOwner paletteOwner; + private bool isDisposed; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - protected PaletteDitherProcessor(PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - this.Definition = definition; + this.dither = definition.Dither; + + ReadOnlySpan sourcePalette = definition.Palette.Span; + this.paletteOwner = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); + Color.ToPixel(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span); + + this.ditherProcessor = new DitherProcessor( + this.Configuration, + this.paletteOwner.Memory, + definition.DitherScale); } - protected PaletteDitherProcessor Definition { get; } + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest); + } /// - protected override void BeforeFrameApply(ImageFrame source) + protected override void Dispose(bool disposing) { - // Lazy init palette: - if (this.palette is null) + if (this.isDisposed) { - ReadOnlySpan sourcePalette = this.Definition.Palette.Span; - this.palette = new TPixel[sourcePalette.Length]; - Color.ToPixel(this.Configuration, sourcePalette, this.palette); + return; } - // Lazy init paletteVector: - if (this.paletteVector is null) + this.isDisposed = true; + if (disposing) { - this.paletteVector = new Vector4[this.palette.Length]; - PixelOperations.Instance.ToVector4( - this.Configuration, - (ReadOnlySpan)this.palette, - (Span)this.paletteVector, - PixelConversionModifiers.Scale); + this.paletteOwner.Dispose(); } - base.BeforeFrameApply(source); + this.paletteOwner = null; + base.Dispose(disposing); } /// - /// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space. + /// Used to allow inlining of calls to + /// . /// - /// The source color to match. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected PixelPair GetClosestPixelPair(ref TPixel pixel) + private readonly struct DitherProcessor : IPaletteDitherImageProcessor { - // Check if the color is in the lookup table - if (this.cache.TryGetValue(pixel, out PixelPair value)) + private readonly EuclideanPixelMap pixelMap; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherProcessor( + Configuration configuration, + ReadOnlyMemory palette, + float ditherScale) { - return value; + this.Configuration = configuration; + this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.Palette = palette; + this.DitherScale = ditherScale; } - return this.GetClosestPixelPairSlow(ref pixel); - } + public Configuration Configuration { get; } - [MethodImpl(MethodImplOptions.NoInlining)] - private PixelPair GetClosestPixelPairSlow(ref TPixel pixel) - { - // Not found - loop through the palette and find the nearest match. - float leastDistance = float.MaxValue; - float secondLeastDistance = float.MaxValue; - var vector = pixel.ToVector4(); + public ReadOnlyMemory Palette { get; } - TPixel closest = default; - TPixel secondClosest = default; - for (int index = 0; index < this.paletteVector.Length; index++) - { - ref Vector4 candidate = ref this.paletteVector[index]; - float distance = Vector4.DistanceSquared(vector, candidate); + public float DitherScale { get; } - if (distance < leastDistance) - { - leastDistance = distance; - secondClosest = closest; - closest = this.palette[index]; - } - else if (distance < secondLeastDistance) - { - secondLeastDistance = distance; - secondClosest = this.palette[index]; - } + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel GetPaletteColor(TPixel color) + { + this.pixelMap.GetClosestColor(color, out TPixel match); + return match; } - - // Pop it into the cache for next time - var pair = new PixelPair(closest, secondClosest); - this.cache.Add(pixel, pair); - - return pair; } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs deleted file mode 100644 index 13660d30ab..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Represents a composite pair of pixels. Used for caching color distance lookups. - /// - /// The pixel format. - internal readonly struct PixelPair : IEquatable> - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the struct. - /// - /// The first pixel color - /// The second pixel color - public PixelPair(TPixel first, TPixel second) - { - this.First = first; - this.Second = second; - } - - /// - /// Gets the first pixel color - /// - public TPixel First { get; } - - /// - /// Gets the second pixel color - /// - public TPixel Second { get; } - - /// - public bool Equals(PixelPair other) - => this.First.Equals(other.First) && this.Second.Equals(other.Second); - - /// - public override bool Equals(object obj) - => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second); - - /// - public override int GetHashCode() => HashCode.Combine(this.First, this.Second); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs deleted file mode 100644 index b489f8f28d..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. - /// - /// - public sealed class Sierra2Diffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix Sierra2Matrix = - new float[,] - { - { 0, 0, 0, 4, 3 }, - { 1, 2, 3, 2, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - public Sierra2Diffuser() - : base(Sierra2Matrix, 16) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs deleted file mode 100644 index 04abc782a6..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. - /// - /// - public sealed class Sierra3Diffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix Sierra3Matrix = - new float[,] - { - { 0, 0, 0, 5, 3 }, - { 2, 4, 5, 4, 2 }, - { 0, 2, 3, 2, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public Sierra3Diffuser() - : base(Sierra3Matrix, 32) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs deleted file mode 100644 index 2ac69cf456..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. - /// - /// - public sealed class SierraLiteDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix SierraLiteMatrix = - new float[,] - { - { 0, 0, 2 }, - { 1, 1, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public SierraLiteDiffuser() - : base(SierraLiteMatrix, 4) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs deleted file mode 100644 index b929a28d30..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. - /// - public sealed class StevensonArceDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix StevensonArceMatrix = - new float[,] - { - { 0, 0, 0, 0, 0, 32, 0 }, - { 12, 0, 26, 0, 30, 0, 16 }, - { 0, 12, 0, 26, 0, 12, 0 }, - { 5, 0, 12, 0, 12, 0, 5 } - }; - - /// - /// Initializes a new instance of the class. - /// - public StevensonArceDiffuser() - : base(StevensonArceMatrix, 200) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs deleted file mode 100644 index bb3aebc3f5..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Stucki image dithering algorithm. - /// - /// - public sealed class StuckiDiffuser : ErrorDiffuser - { - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix StuckiMatrix = - new float[,] - { - { 0, 0, 0, 8, 4 }, - { 2, 4, 8, 4, 2 }, - { 1, 2, 4, 2, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - public StuckiDiffuser() - : base(StuckiMatrix, 42) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt index ea412f6351..27dea8af17 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt +++ b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt @@ -1,3 +1,6 @@ +Reference: +http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/error_diffusion.txt + List of error diffusion schemes. Quantization error of *current* pixel is added to the pixels diff --git a/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf new file mode 100644 index 0000000000..42fb22c959 Binary files /dev/null and b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf differ diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs new file mode 100644 index 0000000000..34a0660495 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Drawing +{ + /// + /// Combines two images together by blending the pixels. + /// + public class DrawImageProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The image to blend. + /// The location to draw the blended image. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. + public DrawImageProcessor( + Image image, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + { + this.Image = image; + this.Location = location; + this.ColorBlendingMode = colorBlendingMode; + this.AlphaCompositionMode = alphaCompositionMode; + this.Opacity = opacity; + } + + /// + /// Gets the image to blend. + /// + public Image Image { get; } + + /// + /// Gets the location to draw the blended image. + /// + public Point Location { get; } + + /// + /// Gets the blending mode to use when drawing the image. + /// + public PixelColorBlendingMode ColorBlendingMode { get; } + + /// + /// Gets the Alpha blending mode to use when drawing the image. + /// + public PixelAlphaCompositionMode AlphaCompositionMode { get; } + + /// + /// Gets the opacity of the image to blend. + /// + public float Opacity { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixelBg : unmanaged, IPixel + { + var visitor = new ProcessorFactoryVisitor(configuration, this, source, sourceRectangle); + this.Image.AcceptVisitor(visitor); + return visitor.Result; + } + + private class ProcessorFactoryVisitor : IImageVisitor + where TPixelBg : unmanaged, IPixel + { + private readonly Configuration configuration; + private readonly DrawImageProcessor definition; + private readonly Image source; + private readonly Rectangle sourceRectangle; + + public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image source, Rectangle sourceRectangle) + { + this.configuration = configuration; + this.definition = definition; + this.source = source; + this.sourceRectangle = sourceRectangle; + } + + public IImageProcessor Result { get; private set; } + + public void Visit(Image image) + where TPixelFg : unmanaged, IPixel + { + this.Result = new DrawImageProcessor( + this.configuration, + image, + this.source, + this.sourceRectangle, + this.definition.Location, + this.definition.ColorBlendingMode, + this.definition.AlphaCompositionMode, + this.definition.Opacity); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs new file mode 100644 index 0000000000..fca896929f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Drawing +{ + /// + /// Combines two images together by blending the pixels. + /// + /// The pixel format of destination image. + /// The pixel format of source image. + internal class DrawImageProcessor : ImageProcessor + where TPixelBg : unmanaged, IPixel + where TPixelFg : unmanaged, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The foreground to blend with the currently processing image. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The location to draw the blended image. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. Must be between 0 and 1. + public DrawImageProcessor( + Configuration configuration, + Image image, + Image source, + Rectangle sourceRectangle, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + : base(configuration, source, sourceRectangle) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + this.Image = image; + this.Opacity = opacity; + this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); + this.Location = location; + } + + /// + /// Gets the image to blend + /// + public Image Image { get; } + + /// + /// Gets the opacity of the image to blend + /// + public float Opacity { get; } + + /// + /// Gets the pixel blender + /// + public PixelBlender Blender { get; } + + /// + /// Gets the location to draw the blended image + /// + public Point Location { get; } + + /// + protected override void OnFrameApply(ImageFrame source) + { + Rectangle sourceRectangle = this.SourceRectangle; + Configuration configuration = this.Configuration; + + Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; + + // Align start/end positions. + Rectangle bounds = targetImage.Bounds(); + + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); + int targetX = minX - this.Location.X; + + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + + int width = maxX - minX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + // Not a valid operation because rectangle does not overlap with this image. + if (workingRect.Width <= 0 || workingRect.Height <= 0) + { + throw new ImageProcessingException( + "Cannot draw image because the source image does not overlap the target image."); + } + + var operation = new RowOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + ParallelRowIterator.IterateRows( + configuration, + workingRect, + in operation); + } + + /// + /// A implementing the draw logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame sourceFrame; + private readonly Image targetImage; + private readonly PixelBlender blender; + private readonly Configuration configuration; + private readonly int minX; + private readonly int width; + private readonly int locationY; + private readonly int targetX; + private readonly float opacity; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + ImageFrame sourceFrame, + Image targetImage, + PixelBlender blender, + Configuration configuration, + int minX, + int width, + int locationY, + int targetX, + float opacity) + { + this.sourceFrame = sourceFrame; + this.targetImage = targetImage; + this.blender = blender; + this.configuration = configuration; + this.minX = minX; + this.width = width; + this.locationY = locationY; + this.targetX = targetX; + this.opacity = opacity; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs new file mode 100644 index 0000000000..626ffd7168 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// An used by the row delegates for a given instance + /// + public interface IPixelRowDelegate + { + /// + /// Applies the current pixel row delegate to a target row of preprocessed pixels. + /// + /// The target row of pixels to process. + /// The initial horizontal and vertical offset for the input pixels to process. + void Invoke(Span span, Point offset); + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 6f9e1869a3..a816b0cb8b 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Effects { @@ -40,10 +39,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects public int BrushSize { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new OilPaintingProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new OilPaintingProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index ea7ba7409d..5ee1e40de0 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Effects { @@ -18,18 +17,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// Adapted from by Dewald Esterhuizen. /// The pixel format. internal class OilPaintingProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly OilPaintingProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public OilPaintingProcessor(OilPaintingProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public OilPaintingProcessor(Configuration configuration, OilPaintingProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.definition = definition; } @@ -43,90 +43,144 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects throw new ArgumentOutOfRangeException(nameof(brushSize)); } - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + source.CopyTo(targetPixels); - int radius = brushSize >> 1; - int levels = this.definition.Levels; + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + this.SourceRectangle, + in operation); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly int radius; + private readonly int levels; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + Buffer2D targetPixels, + ImageFrame source, + Configuration configuration, + int radius, + int levels) { - source.CopyTo(targetPixels); + this.bounds = bounds; + this.targetPixels = targetPixels; + this.source = source; + this.configuration = configuration; + this.radius = radius; + this.levels = levels; + } - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; + + /* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row. + * The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because + * the two allocated buffers have a length equal to the width of the source image, + * and not just equal to the width of the target rectangle to process. + * Furthermore, there are two buffers being allocated in this case, so using that overload would + * have still required the explicit allocation of the secondary buffer. + * Similarly, one temporary float buffer is also allocated from the pool, and that is used + * to create the target bins for all the color channels being processed. + * This buffer is only rented once outside of the main processing loop, and its contents + * are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ + using IMemoryOwner sourceRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); + using IMemoryOwner targetRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); + using IMemoryOwner bins = this.configuration.MemoryAllocator.Allocate(this.levels * 4); + + Span sourceRowVector4Span = sourceRowBuffer.Memory.Span; + Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width); + + Span targetRowVector4Span = targetRowBuffer.Memory.Span; + Span targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width); + + ref float binsRef = ref bins.GetReference(); + ref int intensityBinRef = ref Unsafe.As(ref binsRef); + ref float redBinRef = ref Unsafe.Add(ref binsRef, this.levels); + ref float blueBinRef = ref Unsafe.Add(ref redBinRef, this.levels); + ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, this.levels); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + + for (int x = this.bounds.X; x < this.bounds.Right; x++) + { + int maxIntensity = 0; + int maxIndex = 0; + + // Clear the current shared buffer before processing each target pixel + bins.Memory.Span.Clear(); + + for (int fy = 0; fy <= this.radius; fy++) { - for (int y = rows.Min; y < rows.Max; y++) + int fyr = fy - this.radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); + + for (int fx = 0; fx <= this.radius; fx++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); + int fxr = fx - this.radius; + int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); + + var vector = sourceOffsetRow[offsetX].ToVector4(); + + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; + + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); - for (int x = startX; x < endX; x++) + Unsafe.Add(ref intensityBinRef, currentIntensity)++; + Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; + Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; + Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + + if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) { - int maxIntensity = 0; - int maxIndex = 0; - - var intensityBin = new int[levels]; - var redBin = new float[levels]; - var blueBin = new float[levels]; - var greenBin = new float[levels]; - - for (int fy = 0; fy <= radius; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx <= radius; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); - - var vector = sourceOffsetRow[offsetX].ToVector4(); - - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; - - int currentIntensity = (int)MathF.Round( - (sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); - - intensityBin[currentIntensity]++; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; - - if (intensityBin[currentIntensity] > maxIntensity) - { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; - } - } - - float red = MathF.Abs(redBin[maxIndex] / maxIntensity); - float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); - float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); - - ref TPixel pixel = ref targetRow[x]; - pixel.FromVector4( - new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); - } + maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); + maxIndex = currentIntensity; } } - }); - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity); + float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity); + float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity); + float alpha = sourceRowVector4Span[x].W; + + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); + } + } + + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs new file mode 100644 index 0000000000..3721afee32 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// Applies a user defined row processing delegate to the image. + /// + internal sealed class PixelRowDelegateProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The user defined, row processing delegate. + /// The to apply during the pixel conversions. + public PixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) + { + this.PixelRowOperation = pixelRowOperation; + this.Modifiers = modifiers; + } + + /// + /// Gets the user defined row processing delegate to the image. + /// + public PixelRowOperation PixelRowOperation { get; } + + /// + /// Gets the to apply during the pixel conversions. + /// + public PixelConversionModifiers Modifiers { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } + + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; + + [MethodImpl(InliningOptions.ShortMethod)] + public PixelRowDelegate(PixelRowOperation pixelRowOperation) + { + this.pixelRowOperation = pixelRowOperation; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(Span span, Point offset) => this.pixelRowOperation(span); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs new file mode 100644 index 0000000000..71259a6188 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// The base class for all processors that accept a user defined row processing delegate. + /// + /// The pixel format. + /// The row processor type. + internal sealed class PixelRowDelegateProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + where TDelegate : struct, IPixelRowDelegate + { + private readonly TDelegate rowDelegate; + + /// + /// The to apply during the pixel conversions. + /// + private readonly PixelConversionModifiers modifiers; + + /// + /// Initializes a new instance of the class. + /// + /// The row processor to use to process each pixel row + /// The configuration which allows altering default behaviour or extending the library. + /// The to apply during the pixel conversions. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public PixelRowDelegateProcessor( + in TDelegate rowDelegate, + Configuration configuration, + PixelConversionModifiers modifiers, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.rowDelegate = rowDelegate; + this.modifiers = modifiers; + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); + + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly int startX; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly PixelConversionModifiers modifiers; + private readonly TDelegate rowProcessor; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + ImageFrame source, + Configuration configuration, + PixelConversionModifiers modifiers, + in TDelegate rowProcessor) + { + this.startX = startX; + this.source = source; + this.configuration = configuration; + this.modifiers = modifiers; + this.rowProcessor = rowProcessor; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); + + // Run the user defined pixel shader to the current row of pixels + Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index 2d7cef8fff..ba43ca6174 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Effects { @@ -30,10 +29,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects public int Size { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new PixelateProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new PixelateProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index 53acc351cf..60f5d29b09 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading.Tasks; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Common; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Effects { @@ -17,98 +15,89 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// /// The pixel format. internal class PixelateProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly PixelateProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The . /// The source for the current processor instance. /// The source area to process for the current processor instance. - public PixelateProcessor(PixelateProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } + public PixelateProcessor(Configuration configuration, PixelateProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.definition = definition; private int Size => this.definition.Size; /// protected override void OnFrameApply(ImageFrame source) { - if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width) - { - throw new ArgumentOutOfRangeException(nameof(this.Size)); - } - - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); int size = this.Size; - int offset = this.Size / 2; - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size)); + Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size)); - // Reset offset if necessary. - if (minX > 0) + // Get the range on the y-plane to choose from. + // TODO: It would be nice to be able to pool this somehow but neither Memory nor Span + // implement IEnumerable. + IEnumerable range = EnumerableExtensions.SteppedRange(interest.Y, i => i < interest.Bottom, size); + Parallel.ForEach( + range, + this.Configuration.GetParallelOptions(), + new RowOperation(interest, size, source).Invoke); + } + + private readonly struct RowOperation + { + private readonly int minX; + private readonly int maxX; + private readonly int maxXIndex; + private readonly int maxY; + private readonly int maxYIndex; + private readonly int size; + private readonly int radius; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + int size, + ImageFrame source) { - startX = 0; + this.minX = bounds.X; + this.maxX = bounds.Right; + this.maxXIndex = bounds.Right - 1; + this.maxY = bounds.Bottom; + this.maxYIndex = bounds.Bottom - 1; + this.size = size; + this.radius = size >> 1; + this.source = source; } - if (minY > 0) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - startY = 0; - } + Span rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex)); - // Get the range on the y-plane to choose from. - IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); + for (int x = this.minX; x < this.maxX; x += this.size) + { + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)]; - Parallel.ForEach( - range, - this.Configuration.GetParallelOptions(), - y => + // For each pixel in the pixelate size, set it to the centre color. + for (int oY = y; oY < y + this.size && oY < this.maxY; oY++) { - int offsetY = y - startY; - int offsetPy = offset; - - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) + for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) { - offsetPy--; + this.source[oX, oY] = pixel; } - - Span row = source.GetPixelRowSpan(offsetY + offsetPy); - - for (int x = minX; x < maxX; x += size) - { - int offsetX = x - startX; - int offsetPx = offset; - - while (x + offsetPx >= maxX) - { - offsetPx--; - } - - // Get the pixel color in the centre of the soon to be pixelated area. - TPixel pixel = row[offsetX + offsetPx]; - - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) - { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) - { - source[k, l] = pixel; - } - } - } - }); + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs new file mode 100644 index 0000000000..6822daa424 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// Applies a user defined, position aware, row processing delegate to the image. + /// + internal sealed class PositionAwarePixelRowDelegateProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The user defined, position aware, row processing delegate. + /// The to apply during the pixel conversions. + public PositionAwarePixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) + { + this.PixelRowOperation = pixelRowOperation; + this.Modifiers = modifiers; + } + + /// + /// Gets the user defined, position aware, row processing delegate. + /// + public PixelRowOperation PixelRowOperation { get; } + + /// + /// Gets the to apply during the pixel conversions. + /// + public PixelConversionModifiers Modifiers { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } + + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; + + [MethodImpl(InliningOptions.ShortMethod)] + public PixelRowDelegate(PixelRowOperation pixelRowOperation) + { + this.pixelRowOperation = pixelRowOperation; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(Span span, Point offset) => this.pixelRowOperation(span, offset); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index 9cd4a9e460..5cae98568a 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Filters { @@ -24,10 +22,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters public ColorMatrix Matrix { get; } /// - public virtual IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new FilterProcessor(this, source, sourceRectangle); - } + public virtual IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new FilterProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 5fa233d180..dee9d2ff62 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -3,11 +3,10 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Filters { @@ -16,18 +15,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// /// The pixel format. internal class FilterProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly FilterProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The . /// The source for the current processor instance. /// The source area to process for the current processor instance. - public FilterProcessor(FilterProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public FilterProcessor(Configuration configuration, FilterProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.definition = definition; } @@ -36,27 +36,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startX = interest.X; + var operation = new RowOperation(interest.X, source, this.definition.Matrix, this.Configuration); - ColorMatrix matrix = this.definition.Matrix; - - ParallelHelper.IterateRowsWithTempBuffer( - interest, + ParallelRowIterator.IterateRows( this.Configuration, - (rows, vectorBuffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); - PixelOperations.Instance.ToVector4(this.Configuration, rowSpan, vectorSpan); - - Vector4Utils.Transform(vectorSpan, ref matrix); - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, rowSpan); - } - }); + interest, + in operation); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly int startX; + private readonly ImageFrame source; + private readonly ColorMatrix matrix; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + ImageFrame source, + ColorMatrix matrix, + Configuration configuration) + { + this.startX = startX; + this.source = source; + this.matrix = matrix; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); + + Vector4Utilities.Transform(span, ref Unsafe.AsRef(this.matrix)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs new file mode 100644 index 0000000000..49be3b6a67 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Filters +{ + /// + /// Applies a lightness filter matrix using the given amount. + /// + public sealed class LightnessProcessor : FilterProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public LightnessProcessor(float amount) + : base(KnownFilterMatrices.CreateLightnessFilter(amount)) + { + this.Amount = amount; + } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs index fdfaa9cb07..bb6ea51c12 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// @@ -13,13 +11,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// /// Initializes a new instance of the class. /// - public LomographProcessor() + /// Graphics options to use within the processor. + public LomographProcessor(GraphicsOptions graphicsOptions) : base(KnownFilterMatrices.LomographFilter) { + this.GraphicsOptions = graphicsOptions; } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } + /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) => - new LomographProcessor(this, source, sourceRectangle); + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => + new LomographProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs index 7d3a5bbc0a..0706e9fc8d 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Filters { @@ -11,25 +10,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// Converts the colors of the image recreating an old Lomograph effect. /// internal class LomographProcessor : FilterProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255); + private readonly LomographProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public LomographProcessor(LomographProcessor definition, Image source, Rectangle sourceRectangle) - : base(definition, source, sourceRectangle) + public LomographProcessor(Configuration configuration, LomographProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, definition, source, sourceRectangle) { + this.definition = definition; } /// protected override void AfterImageApply() { - new VignetteProcessor(VeryDarkGreen).Execute(this.Source, this.SourceRectangle); + new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkGreen).Execute(this.Configuration, this.Source, this.SourceRectangle); base.AfterImageApply(); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs new file mode 100644 index 0000000000..95a099106f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Filters +{ + internal sealed class OpaqueProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + public OpaqueProcessor( + Configuration configuration, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + } + + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + var operation = new OpaqueRowOperation(this.Configuration, source, interest); + ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); + } + + private readonly struct OpaqueRowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly ImageFrame target; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public OpaqueRowOperation( + Configuration configuration, + ImageFrame target, + Rectangle bounds) + { + this.configuration = configuration; + this.target = target; + this.bounds = bounds; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span targetRowSpan = this.target.GetPixelRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Scale); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs index c8527a29cf..965a35be15 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// @@ -13,13 +11,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// /// Initializes a new instance of the class. /// - public PolaroidProcessor() + /// Graphics options to use within the processor. + public PolaroidProcessor(GraphicsOptions graphicsOptions) : base(KnownFilterMatrices.PolaroidFilter) { + this.GraphicsOptions = graphicsOptions; } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } + /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) => - new PolaroidProcessor(this, source, sourceRectangle); + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => + new PolaroidProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs index f7ab1a1ec2..470d553c2c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Filters { @@ -11,28 +10,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// Converts the colors of the image recreating an old Polaroid effect. /// internal class PolaroidProcessor : FilterProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private static readonly Color LightOrange = Color.FromRgba(255, 153, 102, 128); - private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0); + private readonly PolaroidProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public PolaroidProcessor(PolaroidProcessor definition, Image source, Rectangle sourceRectangle) - : base(definition, source, sourceRectangle) + public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, definition, source, sourceRectangle) { + this.definition = definition; } /// protected override void AfterImageApply() { - new VignetteProcessor(VeryDarkOrange).Execute(this.Source, this.SourceRectangle); - new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Source, this.SourceRectangle); + new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle); + new GlowProcessor(this.definition.GraphicsOptions, LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle); base.AfterImageApply(); } } diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs index 554a4b8860..2f12e065be 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -16,12 +15,13 @@ namespace SixLabors.ImageSharp.Processing.Processors /// the processing algorithm on an . /// /// The pixel type. + /// The configuration which allows altering default behaviour or extending the library. /// The source image. Cannot be null. /// /// The structure that specifies the portion of the image object to draw. /// /// The - ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs index 84b1262299..988d6d866e 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public interface ICloningImageProcessor : IImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Clones the specified and executes the process against the clone. diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor.cs b/src/ImageSharp/Processing/Processors/IImageProcessor.cs index fb7a6a4d9d..9d2e301de1 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -19,12 +18,13 @@ namespace SixLabors.ImageSharp.Processing.Processors /// the processing algorithm on an . /// /// The pixel type. + /// The configuration which allows altering default behaviour or extending the library. /// The source image. Cannot be null. /// /// The structure that specifies the portion of the image object to draw. /// /// The - IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs index 1b874e4b94..4361936d1a 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public interface IImageProcessor : IDisposable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Executes the process against the specified . diff --git a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs index ce8ed813b5..7e72c7bf03 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -13,26 +12,29 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Executes the processor against the given source image and rectangle bounds. /// /// The processor. + /// The configuration which allows altering default behaviour or extending the library. /// The source image. /// The source bounds. - public static void Execute(this IImageProcessor processor, Image source, Rectangle sourceRectangle) - => source.AcceptVisitor(new ExecuteVisitor(processor, sourceRectangle)); + public static void Execute(this IImageProcessor processor, Configuration configuration, Image source, Rectangle sourceRectangle) + => source.AcceptVisitor(new ExecuteVisitor(configuration, processor, sourceRectangle)); private class ExecuteVisitor : IImageVisitor { + private readonly Configuration configuration; private readonly IImageProcessor processor; private readonly Rectangle sourceRectangle; - public ExecuteVisitor(IImageProcessor processor, Rectangle sourceRectangle) + public ExecuteVisitor(Configuration configuration, IImageProcessor processor, Rectangle sourceRectangle) { + this.configuration = configuration; this.processor = processor; this.sourceRectangle = sourceRectangle; } public void Visit(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (IImageProcessor processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle)) + using (IImageProcessor processorImpl = this.processor.CreatePixelSpecificProcessor(this.configuration, image, this.sourceRectangle)) { processorImpl.Execute(); } diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index 3e46e3c087..964a88d6a1 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -14,18 +12,19 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public abstract class ImageProcessor : IImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The source for the current processor instance. /// The source area to process for the current processor instance. - protected ImageProcessor(Image source, Rectangle sourceRectangle) + protected ImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) { + this.Configuration = configuration; this.Source = source; this.SourceRectangle = sourceRectangle; - this.Configuration = this.Source.GetConfiguration(); } /// @@ -94,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - public virtual void Dispose() + public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs index af3a336a4d..1fdb10661c 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// @@ -17,14 +15,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// Indicating whether to clip the histogram bins at a specific value. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. public AdaptiveHistogramEqualizationProcessor( int luminanceLevels, bool clipHistogram, - float clipLimitPercentage, + int clipLimit, int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimitPercentage) + : base(luminanceLevels, clipHistogram, clipLimit) { this.NumberOfTiles = numberOfTiles; } @@ -35,12 +33,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public int NumberOfTiles { get; } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) { return new AdaptiveHistogramEqualizationProcessor( + configuration, this.LuminanceLevels, this.ClipHistogram, - this.ClipLimitPercentage, + this.ClipLimit, this.NumberOfTiles, source, sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 4cda4030fd..1547de8acf 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -7,12 +7,9 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Normalization { @@ -22,26 +19,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// Indicating whether to clip the histogram bins at a specific value. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. /// The source for the current processor instance. /// The source area to process for the current processor instance. public AdaptiveHistogramEqualizationProcessor( + Configuration configuration, int luminanceLevels, bool clipHistogram, - float clipLimitPercentage, + int clipLimit, int tiles, Image source, Rectangle sourceRectangle) - : base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle) + : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); @@ -81,56 +80,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } - Parallel.For( - 0, - tileYStartPositions.Count, - new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }, - index => - { - int y = tileYStartPositions[index].y; - int cdfYY = tileYStartPositions[index].cdfY; - - // It's unfortunate that we have to do this per iteration. - ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - - int cdfX = 0; - int x = halfTileWidth; - for (int tile = 0; tile < tileCount - 1; tile++) - { - int tileY = 0; - int yEnd = Math.Min(y + tileHeight, sourceHeight); - int xEnd = Math.Min(x + tileWidth, sourceWidth); - for (int dy = y; dy < yEnd; dy++) - { - int dyOffSet = dy * sourceWidth; - int tileX = 0; - for (int dx = x; dx < xEnd; dx++) - { - ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); - float luminanceEqualized = InterpolateBetweenFourTiles( - pixel, - cdfData, - tileCount, - tileCount, - tileX, - tileY, - cdfX, - cdfYY, - tileWidth, - tileHeight, - luminanceLevels); - - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - tileX++; - } - - tileY++; - } - - cdfX++; - x += tileWidth; - } - }); + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); @@ -416,6 +370,95 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private static float LinearInterpolation(float left, float right, float t) => left + ((right - left) * t); + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly CdfTileData cdfData; + private readonly List<(int y, int cdfY)> tileYStartPositions; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int tileCount; + private readonly int halfTileWidth; + private readonly int luminanceLevels; + private readonly ImageFrame source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + CdfTileData cdfData, + List<(int y, int cdfY)> tileYStartPositions, + int tileWidth, + int tileHeight, + int tileCount, + int halfTileWidth, + int luminanceLevels, + ImageFrame source) + { + this.cdfData = cdfData; + this.tileYStartPositions = tileYStartPositions; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.tileCount = tileCount; + this.halfTileWidth = halfTileWidth; + this.luminanceLevels = luminanceLevels; + this.source = source; + this.sourceWidth = source.Width; + this.sourceHeight = source.Height; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + (int y, int cdfY) tileYStartPosition = this.tileYStartPositions[index]; + int y = tileYStartPosition.y; + int cdfYY = tileYStartPosition.cdfY; + + int cdfX = 0; + int x = this.halfTileWidth; + for (int tile = 0; tile < this.tileCount - 1; tile++) + { + int tileY = 0; + int yEnd = Math.Min(y + this.tileHeight, this.sourceHeight); + int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth); + for (int dy = y; dy < yEnd; dy++) + { + int dyOffSet = dy * this.sourceWidth; + int tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenFourTiles( + pixel, + this.cdfData, + this.tileCount, + this.tileCount, + tileX, + tileY, + cdfX, + cdfYY, + this.tileWidth, + this.tileHeight, + this.luminanceLevels); + + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + x += this.tileWidth; + } + } + } + } + /// /// Contains the results of the cumulative distribution function for all tiles. /// @@ -431,7 +474,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly Buffer2D cdfLutBuffer2D; private readonly int pixelsInTile; private readonly int sourceWidth; - private readonly int sourceHeight; private readonly int tileWidth; private readonly int tileHeight; private readonly int luminanceLevels; @@ -453,7 +495,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); this.sourceWidth = sourceWidth; - this.sourceHeight = sourceHeight; this.tileWidth = tileWidth; this.tileHeight = tileHeight; this.pixelsInTile = tileWidth * tileHeight; @@ -470,57 +511,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) { - int sourceWidth = this.sourceWidth; - int sourceHeight = this.sourceHeight; - int tileWidth = this.tileWidth; - int tileHeight = this.tileHeight; - int luminanceLevels = this.luminanceLevels; - - Parallel.For( - 0, - this.tileYStartPositions.Count, - new ParallelOptions { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism }, - index => - { - int cdfX = 0; - int cdfY = this.tileYStartPositions[index].cdfY; - int y = this.tileYStartPositions[index].y; - int endY = Math.Min(y + tileHeight, sourceHeight); - ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); - - using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - for (int x = 0; x < sourceWidth; x += tileWidth) - { - histogram.Clear(); - ref int cdfBase = ref MemoryMarshal.GetReference(this.GetCdfLutSpan(cdfX, index)); - - int xlimit = Math.Min(x + tileWidth, sourceWidth); - for (int dy = y; dy < endY; dy++) - { - int dyOffset = dy * sourceWidth; - for (int dx = x; dx < xlimit; dx++) - { - int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), luminanceLevels); - histogram[luminance]++; - } - } - - if (processor.ClipHistogramEnabled) - { - processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile); - } - - Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - cdfX++; - } - } - }); + var operation = new RowIntervalOperation( + processor, + this.memoryAllocator, + this.cdfMinBuffer2D, + this.cdfLutBuffer2D, + this.tileYStartPositions, + this.tileWidth, + this.tileHeight, + this.luminanceLevels, + source); + + ParallelRowIterator.IterateRowIntervals( + this.configuration, + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + in operation); } [MethodImpl(InliningOptions.ShortMethod)] @@ -548,6 +553,93 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.cdfMinBuffer2D.Dispose(); this.cdfLutBuffer2D.Dispose(); } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly HistogramEqualizationProcessor processor; + private readonly MemoryAllocator allocator; + private readonly Buffer2D cdfMinBuffer2D; + private readonly Buffer2D cdfLutBuffer2D; + private readonly List<(int y, int cdfY)> tileYStartPositions; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int luminanceLevels; + private readonly ImageFrame source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + HistogramEqualizationProcessor processor, + MemoryAllocator allocator, + Buffer2D cdfMinBuffer2D, + Buffer2D cdfLutBuffer2D, + List<(int y, int cdfY)> tileYStartPositions, + int tileWidth, + int tileHeight, + int luminanceLevels, + ImageFrame source) + { + this.processor = processor; + this.allocator = allocator; + this.cdfMinBuffer2D = cdfMinBuffer2D; + this.cdfLutBuffer2D = cdfLutBuffer2D; + this.tileYStartPositions = tileYStartPositions; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.luminanceLevels = luminanceLevels; + this.source = source; + this.sourceWidth = source.Width; + this.sourceHeight = source.Height; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + int cdfX = 0; + int cdfY = this.tileYStartPositions[index].cdfY; + int y = this.tileYStartPositions[index].y; + int endY = Math.Min(y + this.tileHeight, this.sourceHeight); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + + using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + for (int x = 0; x < this.sourceWidth; x += this.tileWidth) + { + histogram.Clear(); + Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); + ref int cdfBase = ref MemoryMarshal.GetReference(cdfLutSpan); + + int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); + for (int dy = y; dy < endY; dy++) + { + int dyOffset = dy * this.sourceWidth; + for (int dx = x; dx < xlimit; dx++) + { + int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), this.luminanceLevels); + histogram[luminance]++; + } + } + + if (this.processor.ClipHistogramEnabled) + { + this.processor.ClipHistogram(histogram, this.processor.ClipLimit); + } + + Unsafe.Add(ref cdfMinBase, cdfX) = this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + cdfX++; + } + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs index 3ff001c522..ff8a6b73d9 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// @@ -16,14 +14,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// Indicating whether to clip the histogram bins at a specific value. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. public AdaptiveHistogramEqualizationSlidingWindowProcessor( int luminanceLevels, bool clipHistogram, - float clipLimitPercentage, + int clipLimit, int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimitPercentage) + : base(luminanceLevels, clipHistogram, clipLimit) { this.NumberOfTiles = numberOfTiles; } @@ -34,15 +32,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public int NumberOfTiles { get; } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new AdaptiveHistogramEqualizationSlidingWindowProcessor( + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new AdaptiveHistogramEqualizationSlidingWindowProcessor( + configuration, this.LuminanceLevels, this.ClipHistogram, - this.ClipLimitPercentage, + this.ClipLimit, this.NumberOfTiles, source, sourceRectangle); - } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index 24ac5ccef2..cce527ad43 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -11,8 +11,6 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Normalization { @@ -21,26 +19,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// Indicating whether to clip the histogram bins at a specific value. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. /// The source for the current processor instance. /// The source area to process for the current processor instance. public AdaptiveHistogramEqualizationSlidingWindowProcessor( + Configuration configuration, int luminanceLevels, bool clipHistogram, - float clipLimitPercentage, + int clipLimit, int tiles, Image source, Rectangle sourceRectangle) - : base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle) + : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); @@ -66,191 +66,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int halfTileWidth = halfTileHeight; var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) - { - // Process the inner tiles, which do not require to check the borders. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: halfTileHeight, - yEnd: source.Height - halfTileHeight, - useFastPath: true, - this.Configuration)); - - // Process the left border of the image. - Parallel.For( - 0, - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - // Process the right border of the image. - Parallel.For( - source.Width - halfTileWidth, - source.Width, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - // Process the top border of the image. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: halfTileHeight, - useFastPath: false, - this.Configuration)); - - // Process the bottom border of the image. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: source.Height - halfTileHeight, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } - } - - /// - /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. - /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and - /// adding a new row at the bottom. - /// - /// The source image. - /// The memory allocator. - /// The target pixels. - /// about the sliding window dimensions. - /// The y start position. - /// The y end position. - /// if set to true the borders of the image will not be checked. - /// The configuration. - /// Action Delegate. - private Action ProcessSlidingWindow( - ImageFrame source, - MemoryAllocator memoryAllocator, - Buffer2D targetPixels, - SlidingWindowInfos swInfos, - int yStart, - int yEnd, - bool useFastPath, - Configuration configuration) - { - return x => - { - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner histogramBufferCopy = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner pixelRowBuffer = memoryAllocator.Allocate(swInfos.TileWidth, AllocationOptions.Clean)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - Span histogramCopy = histogramBufferCopy.GetSpan(); - ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); - - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - - Span pixelRow = pixelRowBuffer.GetSpan(); - ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); - - // Build the initial histogram of grayscale values. - for (int dy = yStart - swInfos.HalfTileHeight; dy < yStart + swInfos.HalfTileHeight; dy++) - { - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); - } - - this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - } - - for (int y = yStart; y < yEnd; y++) - { - if (this.ClipHistogramEnabled) - { - // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. - histogram.CopyTo(histogramCopy); - this.ClipHistogram(histogramCopy, this.ClipLimitPercentage, swInfos.PixelInTile); - } - - // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. - int cdfMin = this.ClipHistogramEnabled - ? this.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) - : this.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = swInfos.PixelInTile - cdfMin; - - // Map the current pixel to the new equalized value. - int luminance = GetLuminance(source[x, y], this.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; - targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[x, y].ToVector4().W)); - - // Remove top most row from the histogram, mirroring rows which exceeds the borders. - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - - this.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - - // Add new bottom row to the histogram, mirroring rows which exceeds the borders. - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - - this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - } - } - }; + // TODO: If the process was able to be switched to operate in parallel rows instead of columns + // then we could take advantage of batching and allocate per-row buffers only once per batch. + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + // Process the inner tiles, which do not require to check the borders. + var innerOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: halfTileHeight, + yEnd: source.Height - halfTileHeight, + useFastPath: true); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + innerOperation.Invoke); + + // Process the left border of the image. + var leftBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + 0, + halfTileWidth, + parallelOptions, + leftBorderOperation.Invoke); + + // Process the right border of the image. + var rightBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + source.Width - halfTileWidth, + source.Width, + parallelOptions, + rightBorderOperation.Invoke); + + // Process the top border of the image. + var topBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: halfTileHeight, + useFastPath: false); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + topBorderOperation.Invoke); + + // Process the bottom border of the image. + var bottomBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: source.Height - halfTileHeight, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + bottomBorderOperation.Invoke); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } /// @@ -349,7 +259,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { for (int idx = 0; idx < length; idx++) { - int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); Unsafe.Add(ref histogramBase, luminance)++; } } @@ -366,11 +276,146 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { for (int idx = 0; idx < length; idx++) { - int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); Unsafe.Add(ref histogramBase, luminance)--; } } + /// + /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. + /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and + /// adding a new row at the bottom. + /// + private readonly struct SlidingWindowOperation + { + private readonly Configuration configuration; + private readonly AdaptiveHistogramEqualizationSlidingWindowProcessor processor; + private readonly ImageFrame source; + private readonly MemoryAllocator memoryAllocator; + private readonly Buffer2D targetPixels; + private readonly SlidingWindowInfos swInfos; + private readonly int yStart; + private readonly int yEnd; + private readonly bool useFastPath; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration. + /// The histogram processor. + /// The source image. + /// The memory allocator. + /// The target pixels. + /// about the sliding window dimensions. + /// The y start position. + /// The y end position. + /// if set to true the borders of the image will not be checked. + [MethodImpl(InliningOptions.ShortMethod)] + public SlidingWindowOperation( + Configuration configuration, + AdaptiveHistogramEqualizationSlidingWindowProcessor processor, + ImageFrame source, + MemoryAllocator memoryAllocator, + Buffer2D targetPixels, + SlidingWindowInfos swInfos, + int yStart, + int yEnd, + bool useFastPath) + { + this.configuration = configuration; + this.processor = processor; + this.source = source; + this.memoryAllocator = memoryAllocator; + this.targetPixels = targetPixels; + this.swInfos = swInfos; + this.yStart = yStart; + this.yEnd = yEnd; + this.useFastPath = useFastPath; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int x) + { + using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner histogramBufferCopy = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner pixelRowBuffer = this.memoryAllocator.Allocate(this.swInfos.TileWidth, AllocationOptions.Clean)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + Span histogramCopy = histogramBufferCopy.GetSpan(); + ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); + + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + + Span pixelRow = pixelRowBuffer.GetSpan(); + ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); + + // Build the initial histogram of grayscale values. + for (int dy = this.yStart - this.swInfos.HalfTileHeight; dy < this.yStart + this.swInfos.HalfTileHeight; dy++) + { + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + + this.processor.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } + + for (int y = this.yStart; y < this.yEnd; y++) + { + if (this.processor.ClipHistogramEnabled) + { + // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. + histogram.CopyTo(histogramCopy); + this.processor.ClipHistogram(histogramCopy, this.processor.ClipLimit); + } + + // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. + int cdfMin = this.processor.ClipHistogramEnabled + ? this.processor.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = this.swInfos.PixelInTile - cdfMin; + + // Map the current pixel to the new equalized value. + int luminance = GetLuminance(this.source[x, y], this.processor.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + this.targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, this.source[x, y].ToVector4().W)); + + // Remove top most row from the histogram, mirroring rows which exceeds the borders. + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + + // Add new bottom row to the histogram, mirroring rows which exceeds the borders. + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } + } + } + } + private class SlidingWindowInfos { public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) @@ -382,15 +427,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.PixelInTile = pixelInTile; } - public int TileWidth { get; private set; } + public int TileWidth { get; } - public int TileHeight { get; private set; } + public int TileHeight { get; } - public int PixelInTile { get; private set; } + public int PixelInTile { get; } - public int HalfTileWidth { get; private set; } + public int HalfTileWidth { get; } - public int HalfTileHeight { get; private set; } + public int HalfTileHeight { get; } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs index dab101fcc2..3b984578b5 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// @@ -15,21 +13,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The number of luminance levels. /// A value indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. - public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) - : base(luminanceLevels, clipHistogram, clipLimitPercentage) + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) + : base(luminanceLevels, clipHistogram, clipLimit) { } /// - public override IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - { - return new GlobalHistogramEqualizationProcessor( + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new GlobalHistogramEqualizationProcessor( + configuration, this.LuminanceLevels, this.ClipHistogram, - this.ClipLimitPercentage, + this.ClipLimit, source, sourceRectangle); - } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 6ae6882479..209135debb 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -6,13 +6,9 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Normalization { @@ -21,26 +17,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// /// Indicating whether to clip the histogram bins at a specific value. - /// Histogram clip limit in percent of the total pixels. Histogram bins which exceed this limit, will be capped at this value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The source for the current processor instance. /// The source area to process for the current processor instance. public GlobalHistogramEqualizationProcessor( + Configuration configuration, int luminanceLevels, bool clipHistogram, - float clipLimitPercentage, + int clipLimit, Image source, Rectangle sourceRectangle) - : base(luminanceLevels, clipHistogram, clipLimitPercentage, source, sourceRectangle) + : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { } @@ -49,64 +47,119 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; - var workingRect = new Rectangle(0, 0, source.Width, source.Height); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Build the histogram of the grayscale levels + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source, this.LuminanceLevels); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in grayscaleOperation); + + Span histogram = histogramBuffer.GetSpan(); + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimit); + } + + using IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + int cdfMin = this.CalculateCdf( + ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), + ref MemoryMarshal.GetReference(histogram), + histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + + // Apply the cdf to each pixel of the image + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in cdfOperation); + } + + /// + /// A implementing the grayscale levels logic for . + /// + private readonly struct GrayscaleLevelsRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner histogramBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + + [MethodImpl(InliningOptions.ShortMethod)] + public GrayscaleLevelsRowOperation( + Rectangle bounds, + IMemoryOwner histogramBuffer, + ImageFrame source, + int luminanceLevels) + { + this.bounds = bounds; + this.histogramBuffer = histogramBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + } - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - // Build the histogram of the grayscale levels. - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - - for (int x = 0; x < workingRect.Width; x++) - { - int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; - } - } - }); - - Span histogram = histogramBuffer.GetSpan(); - if (this.ClipHistogramEnabled) + ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) { - this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels); + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; } + } + } + + /// + /// A implementing the cdf application levels logic for . + /// + private readonly struct CdfApplicationRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner cdfBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; + + [MethodImpl(InliningOptions.ShortMethod)] + public CdfApplicationRowOperation( + Rectangle bounds, + IMemoryOwner cdfBuffer, + ImageFrame source, + int luminanceLevels, + float numberOfPixelsMinusCdfMin) + { + this.bounds = bounds; + this.cdfBuffer = cdfBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; + } - // Calculate the cumulative distribution function, which will map each input pixel to a new value. - int cdfMin = this.CalculateCdf( - ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), - ref MemoryMarshal.GetReference(histogram), - histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - - // Apply the cdf to each pixel of the image - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - - for (int x = 0; x < workingRect.Width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); - int luminance = GetLuminance(pixel, this.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } - } - }); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs index 8ddb4834d9..b55b725a6b 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -20,7 +20,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. Defaults to 256. + /// or 65536 for 16-bit grayscale images. + /// Defaults to 256. /// public int LuminanceLevels { get; set; } = 256; @@ -32,14 +33,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public bool ClipHistogram { get; set; } = false; /// - /// Gets or sets the histogram clip limit in percent of the total pixels in a tile. Histogram bins which exceed this limit, will be capped at this value. - /// Defaults to 0.035f. + /// Gets or sets the histogram clip limit. Adaptive histogram equalization may cause noise to be amplified in near constant + /// regions. To reduce this problem, histogram bins which exceed a given limit will be capped at this value. The exceeding values + /// will be redistributed equally to all other bins. The clipLimit depends on the size of the tiles the image is split into + /// and therefore the image size itself. + /// Defaults to 350. /// - public float ClipLimitPercentage { get; set; } = 0.035f; + /// For more information, see also: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE + public int ClipLimit { get; set; } = 350; /// - /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10. + /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// Defaults to 8. /// - public int NumberOfTiles { get; set; } = 10; + public int NumberOfTiles { get; set; } = 8; } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 01a687ac5c..031b121b91 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Normalization { @@ -17,12 +16,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// Indicates, if histogram bins should be clipped. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. - protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) { this.LuminanceLevels = luminanceLevels; this.ClipHistogram = clipHistogram; - this.ClipLimitPercentage = clipLimitPercentage; + this.ClipLimit = clipLimit; } /// @@ -36,13 +35,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public bool ClipHistogram { get; } /// - /// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// - public float ClipLimitPercentage { get; } + public int ClipLimit { get; } /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; /// /// Creates the that implements the algorithm @@ -60,14 +59,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization processor = new GlobalHistogramEqualizationProcessor( options.LuminanceLevels, options.ClipHistogram, - options.ClipLimitPercentage); + options.ClipLimit); break; case HistogramEqualizationMethod.AdaptiveTileInterpolation: processor = new AdaptiveHistogramEqualizationProcessor( options.LuminanceLevels, options.ClipHistogram, - options.ClipLimitPercentage, + options.ClipLimit, options.NumberOfTiles); break; @@ -75,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization processor = new AdaptiveHistogramEqualizationSlidingWindowProcessor( options.LuminanceLevels, options.ClipHistogram, - options.ClipLimitPercentage, + options.ClipLimit, options.NumberOfTiles); break; @@ -83,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization processor = new GlobalHistogramEqualizationProcessor( options.LuminanceLevels, options.ClipHistogram, - options.ClipLimitPercentage); + options.ClipLimit); break; } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index f8515ece6f..ed0968f7c2 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Normalization { @@ -16,34 +14,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal abstract class HistogramEqualizationProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly float luminanceLevelsFloat; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images /// or 65536 for 16-bit grayscale images. /// Indicates, if histogram bins should be clipped. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// The source for the current processor instance. /// The source area to process for the current processor instance. protected HistogramEqualizationProcessor( + Configuration configuration, int luminanceLevels, bool clipHistogram, - float clipLimitPercentage, + int clipLimit, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + : base(configuration, source, sourceRectangle) { Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); - Guard.MustBeGreaterThan(clipLimitPercentage, 0F, nameof(clipLimitPercentage)); + Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit)); this.LuminanceLevels = luminanceLevels; this.luminanceLevelsFloat = luminanceLevels; this.ClipHistogramEnabled = clipHistogram; - this.ClipLimitPercentage = clipLimitPercentage; + this.ClipLimit = clipLimit; } /// @@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public bool ClipHistogramEnabled { get; } /// - /// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. /// - public float ClipLimitPercentage { get; } + public int ClipLimit { get; } /// /// Calculates the cumulative distribution function. @@ -96,11 +96,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// the values over the clip limit to all other bins equally. /// /// The histogram to apply the clipping. - /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. - /// The numbers of pixels inside the tile. - public void ClipHistogram(Span histogram, float clipLimitPercentage, int pixelCount) + /// Histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + public void ClipHistogram(Span histogram, int clipLimit) { - int clipLimit = (int)MathF.Round(pixelCount * clipLimitPercentage); int sumOverClip = 0; ref int histogramBase = ref MemoryMarshal.GetReference(histogram); @@ -114,6 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } } + // Redistribute the clipped pixels over all bins of the histogram. int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; if (addToEachBin > 0) { @@ -122,6 +121,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization Unsafe.Add(ref histogramBase, i) += addToEachBin; } } + + int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); + if (residual != 0) + { + int residualStep = Math.Max(this.LuminanceLevels / residual, 1); + for (int i = 0; i < this.LuminanceLevels && residual > 0; i += residualStep, residual--) + { + ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); + histogramLevel++; + } + } } /// @@ -133,16 +143,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) { var vector = sourcePixel.ToVector4(); - return GetLuminance(ref vector, luminanceLevels); + return ImageMaths.GetBT709Luminance(ref vector, luminanceLevels); } - - /// - /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. - /// - /// The vector to get the luminance from - /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetLuminance(ref Vector4 vector, int luminanceLevels) - => (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index 4b4c537277..241ec1ebed 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { @@ -14,9 +13,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// Initializes a new instance of the class. /// - /// The to set the background color to. /// The options defining blending algorithm and amount. - public BackgroundColorProcessor(Color color, GraphicsOptions options) + /// The to set the background color to. + public BackgroundColorProcessor(GraphicsOptions options, Color color) { this.Color = color; this.GraphicsOptions = options; @@ -33,10 +32,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays public Color Color { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new BackgroundColorProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BackgroundColorProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index 2459b47069..727e724698 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -3,12 +3,10 @@ using System; using System.Buffers; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { @@ -17,21 +15,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// The pixel format. internal class BackgroundColorProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly BackgroundColorProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public BackgroundColorProcessor(BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } + public BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.definition = definition; /// protected override void OnFrameApply(ImageFrame source) @@ -39,63 +36,67 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays TPixel color = this.definition.Color.ToPixel(); GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Configuration configuration = this.Configuration; + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); + using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); - if (minY > 0) - { - startY = 0; - } + colors.GetSpan().Fill(color); + amount.GetSpan().Fill(graphicsOptions.BlendPercentage); - int width = maxX - minX; + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + var operation = new RowOperation(configuration, interest, blender, amount, colors, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - using (IMemoryOwner colors = source.MemoryAllocator.Allocate(width)) - using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly IMemoryOwner amount; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + PixelBlender blender, + IMemoryOwner amount, + IMemoryOwner colors, + ImageFrame source) { - // Be careful! Do not capture colorSpan & amountSpan in the lambda below! - Span colorSpan = colors.GetSpan(); - Span amountSpan = amount.GetSpan(); - - colorSpan.Fill(color); - amountSpan.Fill(graphicsOptions.BlendPercentage); - - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destination = - source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + this.configuration = configuration; + this.bounds = bounds; + this.blender = blender; + this.amount = amount; + this.colors = colors; + this.source = source; + } - // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one - blender.Blend( - source.Configuration, - destination, - colors.GetSpan(), - destination, - amount.GetSpan()); - } - }); + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destination = + this.source.GetPixelRowSpan(y) + .Slice(this.bounds.X, this.bounds.Width); + + // Switch color & destination in the 2nd and 3rd places because we are + // applying the target color under the current one. + this.blender.Blend( + this.configuration, + destination, + this.colors.GetSpan(), + destination, + this.amount.GetSpan()); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 0958e3aa9e..5e0d1cbf7e 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { @@ -15,39 +13,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// Initializes a new instance of the class. /// - /// The color or the glow. - public GlowProcessor(Color color) - : this(color, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color or the glow. /// The options effecting blending and composition. - public GlowProcessor(Color color, GraphicsOptions options) - : this(color, 0, options) - { - } - - /// - /// Initializes a new instance of the class. - /// /// The color or the glow. - /// The radius of the glow. - internal GlowProcessor(Color color, ValueSize radius) - : this(color, radius, GraphicsOptions.Default) + public GlowProcessor(GraphicsOptions options, Color color) + : this(options, color, 0) { } /// /// Initializes a new instance of the class. /// + /// The options effecting blending and composition. /// The color or the glow. /// The radius of the glow. - /// The options effecting blending and composition. - internal GlowProcessor(Color color, ValueSize radius, GraphicsOptions options) + internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius) { this.GlowColor = color; this.Radius = radius; @@ -67,13 +46,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// Gets the the radius. /// - internal ValueSize Radius { get; } + internal ValueSize Radius { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new GlowProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GlowProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 756e8647ba..fbecbc37ce 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -4,12 +4,10 @@ using System; using System.Buffers; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { @@ -18,20 +16,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// The pixel format. internal class GlowProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly PixelBlender blender; - private readonly GlowProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public GlowProcessor(GlowProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public GlowProcessor(Configuration configuration, GlowProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.definition = definition; this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); @@ -40,76 +38,81 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source) { - // TODO: can we simplify the rectangle calculation? - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; TPixel glowColor = this.definition.GlowColor.ToPixel(); - Vector2 center = Rectangle.Center(this.SourceRectangle); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; - float finalRadius = this.definition.Radius.Calculate(source.Size()); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Vector2 center = Rectangle.Center(interest); + float finalRadius = this.definition.Radius.Calculate(interest.Size); float maxDistance = finalRadius > 0 - ? MathF.Min(finalRadius, this.SourceRectangle.Width * .5F) - : this.SourceRectangle.Width * .5F; + ? MathF.Min(finalRadius, interest.Width * .5F) + : interest.Width * .5F; - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(glowColor); - if (minY > 0) + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly Vector2 center; + private readonly float maxDistance; + private readonly float blendPercent; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame source) { - startY = 0; + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; - - using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - rowColors.GetSpan().Fill(glowColor); - - ParallelHelper.IterateRowsWithTempBuffer( - workingRect, - this.Configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = - (blendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - source.Configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); + Span colorSpan = this.colors.GetSpan(); + + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 2365318f3d..3b16f8bc85 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { @@ -15,18 +13,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// Initializes a new instance of the class. /// - /// The color of the vignette. - public VignetteProcessor(Color color) - : this(color, GraphicsOptions.Default) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color of the vignette. /// The options effecting blending and composition. - public VignetteProcessor(Color color, GraphicsOptions options) + /// The color of the vignette. + public VignetteProcessor(GraphicsOptions options, Color color) { this.VignetteColor = color; this.GraphicsOptions = options; @@ -35,11 +24,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// Initializes a new instance of the class. /// + /// The options effecting blending and composition. /// The color of the vignette. /// The x-radius. /// The y-radius. - /// The options effecting blending and composition. - internal VignetteProcessor(Color color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) + internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY) { this.VignetteColor = color; this.RadiusX = radiusX; @@ -68,10 +57,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays internal ValueSize RadiusY { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new VignetteProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new VignetteProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 8569410d22..378009c400 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -4,12 +4,10 @@ using System; using System.Buffers; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { @@ -18,20 +16,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// The pixel format. internal class VignetteProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly PixelBlender blender; - private readonly VignetteProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public VignetteProcessor(VignetteProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public VignetteProcessor(Configuration configuration, VignetteProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.definition = definition; this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); @@ -40,78 +38,89 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source) { - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); - Vector2 centre = Rectangle.Center(this.SourceRectangle); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + Vector2 center = Rectangle.Center(interest); + float finalRadiusX = this.definition.RadiusX.Calculate(interest.Size); + float finalRadiusY = this.definition.RadiusY.Calculate(interest.Size); - Size sourceSize = source.Size(); - float finalRadiusX = this.definition.RadiusX.Calculate(sourceSize); - float finalRadiusY = this.definition.RadiusY.Calculate(sourceSize); float rX = finalRadiusX > 0 - ? MathF.Min(finalRadiusX, this.SourceRectangle.Width * .5F) - : this.SourceRectangle.Width * .5F; + ? MathF.Min(finalRadiusX, interest.Width * .5F) + : interest.Width * .5F; + float rY = finalRadiusY > 0 - ? MathF.Min(finalRadiusY, this.SourceRectangle.Height * .5F) - : this.SourceRectangle.Height * .5F; + ? MathF.Min(finalRadiusY, interest.Height * .5F) + : interest.Height * .5F; + float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(vignetteColor); + + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - if (minY > 0) + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly Vector2 center; + private readonly float maxDistance; + private readonly float blendPercent; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame source) { - startY = 0; + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; - - using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - rowColors.GetSpan().Fill(vignetteColor); - - ParallelHelper.IterateRowsWithTempBuffer( - workingRect, - this.Configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = (blendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - source.Configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); + Span colorSpan = this.colors.GetSpan(); + + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs new file mode 100644 index 0000000000..775e0aa23f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Gets the closest color to the supplied color based upon the Euclidean distance. + /// + /// The pixel format. + internal readonly struct EuclideanPixelMap + where TPixel : unmanaged, IPixel + { + private readonly Vector4[] vectorCache; + private readonly ConcurrentDictionary distanceCache; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration. + /// The color palette to map from. + [MethodImpl(InliningOptions.ShortMethod)] + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) + { + this.Palette = palette; + this.vectorCache = new Vector4[palette.Length]; + + // Use the same rules across all target frameworks. + this.distanceCache = new ConcurrentDictionary(Environment.ProcessorCount, 31); + PixelOperations.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache); + } + + /// + /// Gets the color palette of this . + /// The palette memory is owned by the palette source that created it. + /// + public ReadOnlyMemory Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } + + /// + /// Returns the closest color in the palette and the index of that pixel. + /// The palette contents must match the one used in the constructor. + /// + /// The color to match. + /// The matched color. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + public int GetClosestColor(TPixel color, out TPixel match) + { + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); + + // Check if the color is in the lookup table + if (!this.distanceCache.TryGetValue(color, out int index)) + { + return this.GetClosestColorSlow(color, ref paletteRef, out match); + } + + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match) + { + // Loop through the palette and find the nearest match. + int index = 0; + float leastDistance = float.MaxValue; + var vector = color.ToVector4(); + ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); + for (int i = 0; i < this.Palette.Length; i++) + { + Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); + float distance = Vector4.DistanceSquared(vector, candidate); + + // If it's an exact match, exit the loop + if (distance == 0) + { + index = i; + break; + } + + if (distance < leastDistance) + { + // Less than... assign. + index = i; + leastDistance = distance; + } + } + + // Now I have the index, pop it into the cache for next time + this.distanceCache[color] = index; + match = Unsafe.Add(ref paletteRef, index); + return index; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs new file mode 100644 index 0000000000..4d75042ea3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs @@ -0,0 +1,151 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Contains utility methods for instances. + /// + public static class FrameQuantizerUtilities + { + /// + /// Helper method for throwing an exception when a frame quantizer palette has + /// been requested but not built yet. + /// + /// The pixel format. + /// The frame quantizer palette. + /// + /// The palette has not been built via + /// + public static void CheckPaletteState(in ReadOnlyMemory palette) + where TPixel : unmanaged, IPixel + { + if (palette.Equals(default)) + { + throw new InvalidOperationException("Frame Quantizer palette has not been built."); + } + } + + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The type of frame quantizer. + /// The pixel format. + /// The frame quantizer. + /// The source image frame to quantize. + /// The bounds within the frame to quantize. + /// + /// A representing a quantized version of the source frame pixels. + /// + public static IndexedImageFrame QuantizeFrame( + ref TFrameQuantizer quantizer, + ImageFrame source, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + var interest = Rectangle.Intersect(source.Bounds(), bounds); + + // Collect the palette. Required before the second pass runs. + quantizer.BuildPalette(source, interest); + + var destination = new IndexedImageFrame( + quantizer.Configuration, + interest.Width, + interest.Height, + quantizer.Palette); + + if (quantizer.Options.Dither is null) + { + SecondPass(ref quantizer, source, destination, interest); + } + else + { + // We clone the image as we don't want to alter the original via error diffusion based dithering. + using ImageFrame clone = source.Clone(); + SecondPass(ref quantizer, clone, destination, interest); + } + + return destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void SecondPass( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + IDither dither = quantizer.Options.Dither; + + if (dither is null) + { + var operation = new RowIntervalOperation( + ref quantizer, + source, + destination, + bounds); + + ParallelRowIterator.IterateRowIntervals( + quantizer.Configuration, + bounds, + in operation); + + return; + } + + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly ImageFrame source; + private readonly IndexedImageFrame destination; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + { + this.quantizer = quantizer; + this.source = source; + this.destination = destination; + this.bounds = bounds; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs deleted file mode 100644 index e6ffecc84d..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Numerics; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// The base class for all implementations - /// - /// The pixel format. - public abstract class FrameQuantizer : IFrameQuantizer - where TPixel : struct, IPixel - { - /// - /// A lookup table for colors - /// - private readonly Dictionary distanceCache = new Dictionary(); - - /// - /// Flag used to indicate whether a single pass or two passes are needed for quantization. - /// - private readonly bool singlePass; - - /// - /// The vector representation of the image palette. - /// - private Vector4[] paletteVector; - - /// - /// Initializes a new instance of the class. - /// - /// The quantizer - /// - /// If true, the quantization process only needs to loop through the source pixels once - /// - /// - /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . - /// - protected FrameQuantizer(IQuantizer quantizer, bool singlePass) - { - Guard.NotNull(quantizer, nameof(quantizer)); - - this.Diffuser = quantizer.Diffuser; - this.Dither = this.Diffuser != null; - this.singlePass = singlePass; - } - - /// - /// Initializes a new instance of the class. - /// - /// The diffuser - /// - /// If true, the quantization process only needs to loop through the source pixels once - /// - /// - /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . - /// - protected FrameQuantizer(IErrorDiffuser diffuser, bool singlePass) - { - this.Diffuser = diffuser; - this.Dither = this.Diffuser != null; - this.singlePass = singlePass; - } - - /// - public IErrorDiffuser Diffuser { get; } - - /// - public bool Dither { get; } - - /// - public virtual void Dispose() - { - } - - /// - public IQuantizedFrame QuantizeFrame(ImageFrame image) - { - Guard.NotNull(image, nameof(image)); - - // Get the size of the source image - int height = image.Height; - int width = image.Width; - - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(image, width, height); - } - - // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = this.GetPalette(); - this.paletteVector = new Vector4[palette.Length]; - PixelOperations.Instance.ToVector4( - image.Configuration, - palette.Span, - (Span)this.paletteVector, - PixelConversionModifiers.Scale); - - var quantizedFrame = new QuantizedFrame(image.MemoryAllocator, width, height, palette); - - Span pixelSpan = quantizedFrame.GetWritablePixelSpan(); - if (this.Dither) - { - // We clone the image as we don't want to alter the original via dithering. - using (ImageFrame clone = image.Clone()) - { - this.SecondPass(clone, pixelSpan, palette.Span, width, height); - } - } - else - { - this.SecondPass(image, pixelSpan, palette.Span, width, height); - } - - return quantizedFrame; - } - - /// - /// Execute the first pass through the pixels in the image to create the palette. - /// - /// The source data. - /// The width in pixels of the image. - /// The height in pixels of the image. - protected virtual void FirstPass(ImageFrame source, int width, int height) - { - } - - /// - /// Returns the closest color from the palette to the given color by calculating the - /// Euclidean distance in the Rgba colorspace. - /// - /// The color. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected byte GetClosestPixel(ref TPixel pixel) - { - // Check if the color is in the lookup table - if (this.distanceCache.TryGetValue(pixel, out byte value)) - { - return value; - } - - return this.GetClosestPixelSlow(ref pixel); - } - - /// - /// Retrieve the palette for the quantized image. - /// - /// - /// - /// - protected abstract ReadOnlyMemory GetPalette(); - - /// - /// Returns the index of the first instance of the transparent color in the palette. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected byte GetTransparentIndex() - { - // Transparent pixels are much more likely to be found at the end of a palette. - int paletteVectorLengthMinus1 = this.paletteVector.Length - 1; - - int index = paletteVectorLengthMinus1; - for (int i = paletteVectorLengthMinus1; i >= 0; i--) - { - ref Vector4 candidate = ref this.paletteVector[i]; - if (candidate.Equals(default)) - { - index = i; - } - } - - return (byte)index; - } - - /// - /// Execute a second pass through the image to assign the pixels to a palette entry. - /// - /// The source image. - /// The output pixel array. - /// The output color palette. - /// The width in pixels of the image. - /// The height in pixels of the image. - protected abstract void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height); - - [MethodImpl(MethodImplOptions.NoInlining)] - private byte GetClosestPixelSlow(ref TPixel pixel) - { - // Loop through the palette and find the nearest match. - int colorIndex = 0; - float leastDistance = float.MaxValue; - Vector4 vector = pixel.ToScaledVector4(); - float epsilon = Constants.EpsilonSquared; - - for (int index = 0; index < this.paletteVector.Length; index++) - { - ref Vector4 candidate = ref this.paletteVector[index]; - float distance = Vector4.DistanceSquared(vector, candidate); - - // Greater... Move on. - if (!(distance < leastDistance)) - { - continue; - } - - colorIndex = index; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance < epsilon) - { - break; - } - } - - // Now I have the index, pop it into the cache for next time - byte result = (byte)colorIndex; - this.distanceCache.Add(pixel, result); - return result; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 54dabab0ae..cc87715ebd 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -12,25 +11,52 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The pixel format. public interface IFrameQuantizer : IDisposable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// - /// Gets a value indicating whether to apply dithering to the output image. + /// Gets the configuration. /// - bool Dither { get; } + Configuration Configuration { get; } /// - /// Gets the error diffusion algorithm to apply to the output image. + /// Gets the quantizer options defining quantization rules. /// - IErrorDiffuser Diffuser { get; } + QuantizerOptions Options { get; } /// - /// Quantize an image frame and return the resulting output pixels. + /// Gets the quantized color palette. /// - /// The image to quantize. + /// + /// The palette has not been built via . + /// + ReadOnlyMemory Palette { get; } + + /// + /// Builds the quantized palette from the given image frame and bounds. + /// + /// The source image frame. + /// The region of interest bounds. + void BuildPalette(ImageFrame source, Rectangle bounds); + + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The source image frame to quantize. + /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the image pixels. + /// A representing a quantized version of the source frame pixels. /// - IQuantizedFrame QuantizeFrame(ImageFrame image); + IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); + + /// + /// Returns the index and color from the quantized palette corresponding to the given color. + /// + /// The color to match. + /// The matched color. + /// The index. + byte GetQuantizedColor(TPixel color, out TPixel match); + + // TODO: Enable bulk operations. + // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs deleted file mode 100644 index 42016459be..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Defines an abstraction to represent a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public interface IQuantizedFrame : IDisposable - where TPixel : struct, IPixel - { - /// - /// Gets the width of this . - /// - int Width { get; } - - /// - /// Gets the height of this . - /// - int Height { get; } - - /// - /// Gets the color palette of this . - /// - ReadOnlyMemory Palette { get; } - - /// - /// Gets the pixels of this . - /// - /// The The pixel span. - ReadOnlySpan GetPixelSpan(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index f1490a6d2b..01e4b5e8a2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -12,27 +11,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public interface IQuantizer { /// - /// Gets the error diffusion algorithm to apply to the output image. + /// Gets the quantizer options defining quantization rules. /// - IErrorDiffuser Diffuser { get; } + QuantizerOptions Options { get; } /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The to configure internal operations. /// The pixel format. - /// The + /// The . IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The pixel format. /// The to configure internal operations. - /// The maximum number of colors to hold in the color palette. - /// The - IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel; + /// The options to create the quantizer with. + /// The . + IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 85a4d20295..ce2e406d4c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -1,12 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; +using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -16,167 +17,128 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class OctreeFrameQuantizer : FrameQuantizer - where TPixel : struct, IPixel + public struct OctreeFrameQuantizer : IFrameQuantizer + where TPixel : unmanaged, IPixel { - /// - /// Maximum allowed color depth - /// - private readonly int colors; - - /// - /// Stores the tree - /// + private readonly int maxColors; private readonly Octree octree; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; + private bool isDisposed; /// - /// The transparent index + /// Initializes a new instance of the struct. /// - private byte transparentIndex; - - /// - /// Initializes a new instance of the class. - /// - /// The octree quantizer - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// - public OctreeFrameQuantizer(OctreeQuantizer quantizer) - : this(quantizer, quantizer.MaxColors) + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + + this.maxColors = this.Options.MaxColors; + this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8)); + this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; + this.pixelMap = default; + this.isDithering = !(this.Options.Dither is null); + this.isDisposed = false; } - /// - /// Initializes a new instance of the class. - /// - /// The octree quantizer. - /// The maximum number of colors to hold in the color palette. - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// - public OctreeFrameQuantizer(OctreeQuantizer quantizer, int maxColors) - : base(quantizer, false) - { - this.colors = maxColors; - this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); - } + /// + public Configuration Configuration { get; } + + /// + public QuantizerOptions Options { get; } /// - protected override void FirstPass(ImageFrame source, int width, int height) + public ReadOnlyMemory Palette { - // Loop through each row - for (int y = 0; y < height; y++) + get { - Span row = source.GetPixelRowSpan(y); - ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); - - // And loop through each column - for (int x = 0; x < width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); - - // Add the color to the Octree - this.octree.AddColor(ref pixel); - } + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; } } /// - protected override void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height) + [MethodImpl(InliningOptions.ShortMethod)] + public void BuildPalette(ImageFrame source, Rectangle bounds) { - // Load up the values for the first pixel. We can use these to speed up the second - // pass of the algorithm by avoiding transforming rows of identical color. - TPixel sourcePixel = source[0, 0]; - TPixel previousPixel = sourcePixel; - this.transparentIndex = this.GetTransparentIndex(); - byte pixelValue = this.QuantizePixel(ref sourcePixel); - TPixel transformedPixel = palette[pixelValue]; - - for (int y = 0; y < height; y++) + using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); + + // Loop through each row + for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetPixelRowSpan(y); + Span row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - // And loop through each column - for (int x = 0; x < width; x++) + for (int x = 0; x < bufferSpan.Length; x++) { - // Get the pixel. - sourcePixel = row[x]; + Rgba32 rgba = bufferSpan[x]; - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - // Quantize the pixel - pixelValue = this.QuantizePixel(ref sourcePixel); + // Add the color to the Octree + this.octree.AddColor(rgba); + } + } - // And setup the previous pointer - previousPixel = sourcePixel; + Span paletteSpan = this.paletteOwner.GetSpan(); + int paletteIndex = 0; + this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); - if (this.Dither) - { - transformedPixel = palette[pixelValue]; - } - } + // Length of reduced palette + transparency. + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - if (this.Dither) - { - // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); - } - - output[(y * source.Width) + x] = pixelValue; - } - } + this.palette = result; } - internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); - /// - protected override ReadOnlyMemory GetPalette() => this.octree.Palletize(this.colors); + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); - /// - /// Process the pixel in the second pass of the algorithm. - /// - /// The pixel to quantize. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(ref TPixel pixel) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - if (this.Dither) + // Octree only maps the RGB component of a color + // so cannot tell the difference between a fully transparent + // pixel and a black one. + if (this.isDithering || color.Equals(default)) { - // The colors have changed so we need to use Euclidean distance calculation to - // find the closest value. - return this.GetClosestPixel(ref pixel); + return (byte)this.pixelMap.GetClosestColor(color, out match); } - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - if (rgba.Equals(default)) + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + var index = (byte)this.octree.GetPaletteIndex(color); + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + /// + public void Dispose() + { + if (!this.isDisposed) { - return this.transparentIndex; + this.isDisposed = true; + this.paletteOwner.Dispose(); + this.paletteOwner = null; } - - return (byte)this.octree.GetPaletteIndex(ref pixel); } /// - /// Class which does the actual quantization + /// Class which does the actual quantization. /// - private class Octree + private sealed class Octree { - /// - /// Mask used when getting the appropriate pixels for a given node - /// - // ReSharper disable once StaticMemberInGenericType - private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; - /// /// The root of the Octree /// @@ -195,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Cache the previous color quantized /// - private TPixel previousColor; + private Rgba32 previousColor; /// /// Initializes a new instance of the class. @@ -213,15 +175,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.previousNode = null; } + /// + /// Gets the mask used when getting the appropriate pixels for a given node. + /// + private static ReadOnlySpan Mask => new byte[] + { + 0b10000000, + 0b1000000, + 0b100000, + 0b10000, + 0b1000, + 0b100, + 0b10, + 0b1 + }; + /// /// Gets or sets the number of leaves in the tree /// public int Leaves { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get; - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] set; } @@ -230,73 +207,71 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private OctreeNode[] ReducibleNodes { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get; } /// /// Add a given color value to the Octree /// - /// The pixel data. - public void AddColor(ref TPixel pixel) + /// The color to add. + public void AddColor(Rgba32 color) { // Check if this request is for the same color as the last - if (this.previousColor.Equals(pixel)) + if (this.previousColor.Equals(color)) { - // If so, check if I have a previous node setup. This will only occur if the first color in the image + // If so, check if I have a previous node setup. + // This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (this.previousNode is null) { - this.previousColor = pixel; - this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } else { // Just update the previous node - this.previousNode.Increment(ref pixel); + this.previousNode.Increment(ref color); } } else { - this.previousColor = pixel; - this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } } /// /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors /// + /// The palette to fill. /// The maximum number of colors - /// - /// An with the palletized colors - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TPixel[] Palletize(int colorCount) + /// The palette index, used to calculate the final size of the palette. + [MethodImpl(InliningOptions.ShortMethod)] + public void Palletize(Span palette, int colorCount, ref int paletteIndex) { while (this.Leaves > colorCount - 1) { this.Reduce(); } - // Now palletize the nodes - var palette = new TPixel[colorCount]; - - int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); - - // And return the palette - return palette; } /// /// Get the palette index for the passed color /// - /// The pixel data. + /// The color to match. /// - /// The . + /// The index. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetPaletteIndex(ref TPixel pixel) => this.root.GetPaletteIndex(ref pixel, 0); + [MethodImpl(InliningOptions.ShortMethod)] + public int GetPaletteIndex(TPixel color) + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + return this.root.GetPaletteIndex(ref rgba, 0); + } /// /// Keep track of the previous node that was quantized @@ -304,8 +279,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The node last quantized /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void TrackPrevious(OctreeNode node) => this.previousNode = node; + [MethodImpl(InliningOptions.ShortMethod)] + public void TrackPrevious(OctreeNode node) => this.previousNode = node; /// /// Reduce the depth of the tree @@ -334,7 +309,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Class which encapsulates each node in the tree /// - protected class OctreeNode + public sealed class OctreeNode { /// /// Pointers to any child nodes @@ -374,15 +349,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// - /// The level in the tree = 0 - 7 - /// - /// - /// The number of significant color bits in the image - /// - /// - /// The tree to which this node belongs - /// + /// The level in the tree = 0 - 7. + /// The number of significant color bits in the image. + /// The tree to which this node belongs. public OctreeNode(int level, int colorBits, Octree octree) { // Construct the new node @@ -412,23 +381,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public OctreeNode NextReducible { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get; } /// /// Add a color into the tree /// - /// The pixel color - /// The number of significant color bits - /// The level in the tree - /// The tree to which this node belongs - public void AddColor(ref TPixel pixel, int colorBits, int level, Octree octree) + /// The color to add. + /// The number of significant color bits. + /// The level in the tree. + /// The tree to which this node belongs. + public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf if (this.leaf) { - this.Increment(ref pixel); + this.Increment(ref color); // Setup the previous node octree.TrackPrevious(this); @@ -436,13 +405,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization else { // Go to the next level down in the tree - int shift = 7 - level; - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - - int index = ((rgba.B & Mask[level]) >> (shift - 2)) - | ((rgba.G & Mask[level]) >> (shift - 1)) - | ((rgba.R & Mask[level]) >> shift); + int index = GetColorIndex(ref color, level); OctreeNode child = this.children[index]; if (child is null) @@ -453,7 +416,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // Add the color to the child node - child.AddColor(ref pixel, colorBits, level + 1, octree); + child.AddColor(ref color, colorBits, level + 1, octree); } } @@ -493,13 +456,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The palette /// The current palette index - [MethodImpl(MethodImplOptions.NoInlining)] - public void ConstructPalette(TPixel[] palette, ref int index) + [MethodImpl(InliningOptions.ColdPath)] + public void ConstructPalette(Span palette, ref int index) { if (this.leaf) { // Set the color of the palette entry - var vector = Vector3.Clamp(new Vector3(this.red, this.green, this.blue) / this.pixelCount, Vector3.Zero, new Vector3(255)); + var vector = Vector3.Clamp( + new Vector3(this.red, this.green, this.blue) / this.pixelCount, + Vector3.Zero, + new Vector3(255)); + TPixel pixel = default; pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); palette[index] = pixel; @@ -525,29 +492,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The representing the index of the pixel in the palette. /// - [MethodImpl(MethodImplOptions.NoInlining)] - public int GetPaletteIndex(ref TPixel pixel, int level) + [MethodImpl(InliningOptions.ColdPath)] + public int GetPaletteIndex(ref Rgba32 pixel, int level) { - int index = this.paletteIndex; - - if (!this.leaf) + if (this.leaf) { - int shift = 7 - level; - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); + return this.paletteIndex; + } - int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) - | ((rgba.G & Mask[level]) >> (shift - 1)) - | ((rgba.R & Mask[level]) >> shift); + int colorIndex = GetColorIndex(ref pixel, level); + OctreeNode child = this.children[colorIndex]; - OctreeNode child = this.children[pixelIndex]; - if (child != null) - { - index = child.GetPaletteIndex(ref pixel, level + 1); - } - else + int index = 0; + if (child != null) + { + index = child.GetPaletteIndex(ref pixel, level + 1); + } + else + { + // Check other children. + for (int i = 0; i < this.children.Length; i++) { - throw new Exception($"Cannot retrieve a pixel at the given index {pixelIndex}."); + child = this.children[i]; + if (child != null) + { + var childIndex = child.GetPaletteIndex(ref pixel, level + 1); + if (childIndex != 0) + { + return childIndex; + } + } } } @@ -555,20 +529,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Increment the pixel count and add to the color information + /// Gets the color index at the given level. + /// + /// The color. + /// The node level. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetColorIndex(ref Rgba32 color, int level) + { + DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level)); + + int shift = 7 - level; + ref byte maskRef = ref MemoryMarshal.GetReference(Mask); + byte mask = Unsafe.Add(ref maskRef, level); + + return ((color.R & mask) >> shift) + | ((color.G & mask) >> (shift - 1)) + | ((color.B & mask) >> (shift - 2)); + } + + /// + /// Increment the color count and add to the color information /// - /// The pixel to add. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Increment(ref TPixel pixel) + /// The pixel to add. + [MethodImpl(InliningOptions.ShortMethod)] + public void Increment(ref Rgba32 color) { - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); this.pixelCount++; - this.red += rgba.R; - this.green += rgba.G; - this.blue += rgba.B; + this.red += color.R; + this.green += color.G; + this.blue += color.B; } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index f5fa8c95d9..9e04edef0b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -1,98 +1,48 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using Octrees. /// - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class OctreeQuantizer : IQuantizer { - /// - /// Initializes a new instance of the class. - /// - public OctreeQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(int maxColors) - : this(GetDiffuser(true), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image. - public OctreeQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of colors to hold in the color palette. - /// Whether to apply dithering to the output image. - public OctreeQuantizer(bool dither, int maxColors) - : this(GetDiffuser(dither), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffusion algorithm, if any, to apply to the output image. - public OctreeQuantizer(IErrorDiffuser diffuser) - : this(diffuser, QuantizerConstants.MaxColors) + public OctreeQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image. - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) + /// The quantizer options defining quantization rules. + public OctreeQuantizer(QuantizerOptions options) { - this.Diffuser = diffuser; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the maximum number of colors to hold in the color palette. - /// - public int MaxColors { get; } + public QuantizerOptions Options { get; } - /// /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel - => new OctreeFrameQuantizer(this); + where TPixel : unmanaged, IPixel + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel - { - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new OctreeFrameQuantizer(this, maxColors); - } - - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new OctreeFrameQuantizer(configuration, options); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 265c343e68..ade73e2d02 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -1,12 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -15,87 +12,59 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class PaletteFrameQuantizer : FrameQuantizer - where TPixel : struct, IPixel + internal struct PaletteFrameQuantizer : IFrameQuantizer + where TPixel : unmanaged, IPixel { - /// - /// The reduced image palette. - /// - private readonly ReadOnlyMemory palette; + private readonly EuclideanPixelMap pixelMap; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// - /// The palette quantizer. - /// An array of all colors in the palette. - public PaletteFrameQuantizer(IErrorDiffuser diffuser, ReadOnlyMemory colors) - : base(diffuser, true) => this.palette = colors; - - /// - protected override void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height) + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + /// The pixel map for looking up color matches from a predefined palette. + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteFrameQuantizer( + Configuration configuration, + QuantizerOptions options, + EuclideanPixelMap pixelMap) { - // Load up the values for the first pixel. We can use these to speed up the second - // pass of the algorithm by avoiding transforming rows of identical color. - TPixel sourcePixel = source[0, 0]; - TPixel previousPixel = sourcePixel; - byte pixelValue = this.QuantizePixel(ref sourcePixel); - ref TPixel paletteRef = ref MemoryMarshal.GetReference(palette); - TPixel transformedPixel = Unsafe.Add(ref paletteRef, pixelValue); - - for (int y = 0; y < height; y++) - { - ref TPixel rowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); - // And loop through each column - for (int x = 0; x < width; x++) - { - // Get the pixel. - sourcePixel = Unsafe.Add(ref rowRef, x); + this.Configuration = configuration; + this.Options = options; + this.pixelMap = pixelMap; + } - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - // Quantize the pixel - pixelValue = this.QuantizePixel(ref sourcePixel); + /// + public Configuration Configuration { get; } - // And setup the previous pointer - previousPixel = sourcePixel; + /// + public QuantizerOptions Options { get; } - if (this.Dither) - { - transformedPixel = Unsafe.Add(ref paletteRef, pixelValue); - } - } + /// + public ReadOnlyMemory Palette => this.pixelMap.Palette; - if (this.Dither) - { - // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); - output[(y * source.Width) + x] = pixelValue; - } - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void BuildPalette(ImageFrame source, Rectangle bounds) + { } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override ReadOnlyMemory GetPalette() => this.palette; + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + => (byte)this.pixelMap.GetClosestColor(color, out match); - /// - /// Process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(ref TPixel pixel) => this.GetClosestPixel(ref pixel); + /// + public void Dispose() + { + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 17734bcdc0..c14ea6153f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -2,80 +2,65 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using color palettes. - /// Override this class to provide your own palette. - /// - /// By default the quantizer uses dithering. - /// /// public class PaletteQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + private readonly ReadOnlyMemory colorPalette; + /// /// Initializes a new instance of the class. /// - /// The palette. + /// The color palette. public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, true) + : this(palette, DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The palette. - /// Whether to apply dithering to the output image - public PaletteQuantizer(ReadOnlyMemory palette, bool dither) - : this(palette, GetDiffuser(dither)) + /// The color palette. + /// The quantizer options defining quantization rules. + public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) { - } + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + Guard.NotNull(options, nameof(options)); - /// - /// Initializes a new instance of the class. - /// - /// The palette. - /// The error diffusion algorithm, if any, to apply to the output image - public PaletteQuantizer(ReadOnlyMemory palette, IErrorDiffuser diffuser) - { - this.Palette = palette; - this.Diffuser = diffuser; + this.colorPalette = palette; + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the palette. - /// - public ReadOnlyMemory Palette { get; } + public QuantizerOptions Options { get; } /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel - { - var palette = new TPixel[this.Palette.Length]; - Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan()); - return new PaletteFrameQuantizer(this.Diffuser, palette); - } + where TPixel : unmanaged, IPixel + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel { - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - int max = Math.Min(maxColors, this.Palette.Length); + Guard.NotNull(options, nameof(options)); - var palette = new TPixel[max]; - Color.ToPixel(configuration, this.Palette.Span.Slice(0, max), palette.AsSpan()); - return new PaletteFrameQuantizer(this.Diffuser, palette); - } + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + int length = Math.Min(this.colorPalette.Length, options.MaxColors); + var palette = new TPixel[length]; - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); + + var pixelMap = new EuclideanPixelMap(configuration, palette); + return new PaletteFrameQuantizer(configuration, options, pixelMap); + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs index 8cc14da675..bdaeb57f6d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -16,9 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The quantizer used to reduce the color palette. public QuantizeProcessor(IQuantizer quantizer) - { - this.Quantizer = quantizer; - } + => this.Quantizer = quantizer; /// /// Gets the quantizer. @@ -26,10 +23,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public IQuantizer Quantizer { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new QuantizeProcessor(this.Quantizer, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new QuantizeProcessor(configuration, this.Quantizer, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 9309467229..386caf1be3 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -14,18 +14,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The pixel format. internal class QuantizeProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly IQuantizer quantizer; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The quantizer used to reduce the color palette. /// The source for the current processor instance. /// The source area to process for the current processor instance. - public QuantizeProcessor(IQuantizer quantizer, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public QuantizeProcessor(Configuration configuration, IQuantizer quantizer, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { Guard.NotNull(quantizer, nameof(quantizer)); this.quantizer = quantizer; @@ -34,28 +35,51 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// protected override void OnFrameApply(ImageFrame source) { + var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); + Configuration configuration = this.Configuration; - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration)) - using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source)) - { - int paletteCount = quantized.Palette.Length - 1; + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(source, interest); - // Not parallel to remove "quantized" closure allocation. - // We can operate directly on the source here as we've already read it to get the - // quantized result - for (int y = 0; y < source.Height; y++) - { - Span row = source.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = quantized.GetPixelSpan(); + var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); + ParallelRowIterator.IterateRowIntervals( + configuration, + interest, + in operation); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly IndexedImageFrame quantized; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + ImageFrame source, + IndexedImageFrame quantized) + { + this.bounds = bounds; + this.source = source; + this.quantized = quantized; + } - ReadOnlySpan paletteSpan = quantized.Palette.Span; + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; - int yy = y * source.Width; + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + ReadOnlySpan quantizedRow = this.quantized.GetPixelRowSpan(y - offsetY); - for (int x = 0; x < source.Width; x++) + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + row[x] = paletteSpan[quantizedRow[x - offsetX]]; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs deleted file mode 100644 index fa3d36e10a..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Contains extension methods for . - /// - public static class QuantizedFrameExtensions - { - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. - /// - /// The . - /// The row. - /// The pixel type. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public static ReadOnlySpan GetRowSpan(this IQuantizedFrame frame, int rowIndex) - where TPixel : struct, IPixel - => frame.GetPixelSpan().Slice(rowIndex * frame.Width, frame.Width); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs deleted file mode 100644 index cbea82c1f3..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Represents a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public class QuantizedFrame : IQuantizedFrame - where TPixel : struct, IPixel - { - private IMemoryOwner pixels; - - /// - /// Initializes a new instance of the class. - /// - /// Used to allocated memory for image processing operations. - /// The image width. - /// The image height. - /// The color palette. - internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlyMemory palette) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Width = width; - this.Height = height; - this.Palette = palette; - this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); - } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette { get; private set; } - - /// - /// Gets the pixels of this . - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPixelSpan() => this.pixels.GetSpan(); - - /// - public void Dispose() - { - this.pixels?.Dispose(); - this.pixels = null; - this.Palette = null; - } - - /// - /// Get the non-readonly span of pixel data so can fill it. - /// - internal Span GetWritablePixelSpan() => this.pixels.GetSpan(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs index d79a91c301..ece3777e0e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -1,12 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing.Processors.Dithering; + namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Contains color quantization specific constants. /// - internal static class QuantizerConstants + public static class QuantizerConstants { /// /// The minimum number of colors to use when quantizing an image. @@ -17,5 +19,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The maximum number of colors to use when quantizing an image. /// public const int MaxColors = 256; + + /// + /// The minumim dithering scale used to adjust the amount of dither. + /// + public const float MinDitherScale = 0; + + /// + /// The max dithering scale used to adjust the amount of dither. + /// + public const float MaxDitherScale = 1F; + + /// + /// Gets the default dithering algorithm to use. + /// + public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs new file mode 100644 index 0000000000..5c1daf183b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Defines options for quantization. + /// + public class QuantizerOptions + { + private float ditherScale = QuantizerConstants.MaxDitherScale; + private int maxColors = QuantizerConstants.MaxColors; + + /// + /// Gets or sets the algorithm to apply to the output image. + /// Defaults to ; set to for no dithering. + /// + public IDither Dither { get; set; } = QuantizerConstants.DefaultDither; + + /// + /// Gets or sets the dithering scale used to adjust the amount of dither. Range 0..1. + /// Defaults to . + /// + public float DitherScale + { + get { return this.ditherScale; } + set { this.ditherScale = value.Clamp(QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); } + } + + /// + /// Gets or sets the maximum number of colors to hold in the color palette. Range 0..256. + /// Defaults to . + /// + public int MaxColors + { + get { return this.maxColors; } + set { this.maxColors = value.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index c912572f0e..d95ed5aab9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -10,30 +10,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WebSafePaletteQuantizer : PaletteQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WebSafePaletteQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WebSafePaletteQuantizer(bool dither) - : base(Color.WebSafePalette, dither) + public WebSafePaletteQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - public WebSafePaletteQuantizer(IErrorDiffuser diffuser) - : base(Color.WebSafePalette, diffuser) + /// The quantizer options defining quantization rules. + public WebSafePaletteQuantizer(QuantizerOptions options) + : base(Color.WebSafePalette, options) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index cd320a9a36..8f8e38dd9d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors.Dithering; - namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -11,30 +9,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WernerPaletteQuantizer : PaletteQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WernerPaletteQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WernerPaletteQuantizer(bool dither) - : base(Color.WernerPalette, dither) + public WernerPaletteQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - public WernerPaletteQuantizer(IErrorDiffuser diffuser) - : base(Color.WernerPalette, diffuser) + /// The quantizer options defining quantization rules. + public WernerPaletteQuantizer(QuantizerOptions options) + : base(Color.WernerPalette, options) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 87d696dc91..d15db74e62 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -9,10 +9,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -// TODO: Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? -// (T, R, G, B, A, M2) could be grouped together! Investigate a ColorMoment struct. namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -35,9 +32,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class WuFrameQuantizer : FrameQuantizer - where TPixel : struct, IPixel + internal struct WuFrameQuantizer : IFrameQuantizer + where TPixel : unmanaged, IPixel { + private readonly MemoryAllocator memoryAllocator; + // The following two variables determine the amount of bits to preserve when calculating the histogram. // Reducing the value of these numbers the granularity of the color maps produced, making it much faster // and using much less memory but potentially less accurate. Current results are very good though! @@ -67,204 +66,124 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - /// - /// Moment of P(c). - /// - private IMemoryOwner vwt; - - /// - /// Moment of r*P(c). - /// - private IMemoryOwner vmr; - - /// - /// Moment of g*P(c). - /// - private IMemoryOwner vmg; - - /// - /// Moment of b*P(c). - /// - private IMemoryOwner vmb; - - /// - /// Moment of a*P(c). - /// - private IMemoryOwner vma; - - /// - /// Moment of c^2*P(c). - /// - private IMemoryOwner m2; - - /// - /// Color space tag. - /// - private IMemoryOwner tag; - - /// - /// Maximum allowed color depth - /// - private int colors; - - /// - /// The reduced image palette - /// - private TPixel[] palette; - - /// - /// The color cube representing the image palette - /// - private Box[] colorCube; + private IMemoryOwner momentsOwner; + private IMemoryOwner tagsOwner; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private int maxColors; + private readonly Box[] colorCube; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; + private bool isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// - /// The . - /// The wu quantizer - /// - /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, - /// the second pass quantizes a color based on the position in the histogram. - /// - public WuFrameQuantizer(MemoryAllocator memoryAllocator, WuQuantizer quantizer) - : this(memoryAllocator, quantizer, quantizer.MaxColors) + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public WuFrameQuantizer(Configuration configuration, QuantizerOptions options) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + this.maxColors = this.Options.MaxColors; + this.memoryAllocator = this.Configuration.MemoryAllocator; + this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.paletteOwner = this.memoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; + this.colorCube = new Box[this.maxColors]; + this.isDisposed = false; + this.pixelMap = default; + this.isDithering = this.isDithering = !(this.Options.Dither is null); } - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The wu quantizer. - /// The maximum number of colors to hold in the color palette. - /// - /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, - /// the second pass quantizes a color based on the position in the histogram. - /// - public WuFrameQuantizer(MemoryAllocator memoryAllocator, WuQuantizer quantizer, int maxColors) - : base(quantizer, false) - { - Guard.NotNull(memoryAllocator, nameof(memoryAllocator)); - - this.vwt = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vmr = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vmg = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vmb = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vma = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.m2 = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.tag = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + /// + public Configuration Configuration { get; } - this.colors = maxColors; - } + /// + public QuantizerOptions Options { get; } /// - public override void Dispose() + public ReadOnlyMemory Palette { - this.vwt?.Dispose(); - this.vmr?.Dispose(); - this.vmg?.Dispose(); - this.vmb?.Dispose(); - this.vma?.Dispose(); - this.m2?.Dispose(); - this.tag?.Dispose(); - - this.vwt = null; - this.vmr = null; - this.vmg = null; - this.vmb = null; - this.vma = null; - this.m2 = null; - this.tag = null; + get + { + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } } - internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); - /// - protected override ReadOnlyMemory GetPalette() + public void BuildPalette(ImageFrame source, Rectangle bounds) { - if (this.palette is null) - { - this.palette = new TPixel[this.colors]; - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - - for (int k = 0; k < this.colors; k++) - { - this.Mark(ref this.colorCube[k], (byte)k); + this.Build3DHistogram(source, bounds); + this.Get3DMoments(this.memoryAllocator); + this.BuildCube(); - float weight = Volume(ref this.colorCube[k], vwtSpan); + ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + Span paletteSpan = this.paletteOwner.GetSpan(); + for (int k = 0; k < this.maxColors; k++) + { + this.Mark(ref this.colorCube[k], (byte)k); - if (MathF.Abs(weight) > Constants.Epsilon) - { - float r = Volume(ref this.colorCube[k], vmrSpan); - float g = Volume(ref this.colorCube[k], vmgSpan); - float b = Volume(ref this.colorCube[k], vmbSpan); - float a = Volume(ref this.colorCube[k], vmaSpan); + Moment moment = Volume(ref this.colorCube[k], momentsSpan); - ref TPixel color = ref this.palette[k]; - color.FromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); - } + if (moment.Weight > 0) + { + ref TPixel color = ref paletteSpan[k]; + color.FromScaledVector4(moment.Normalize()); } } - return this.palette; + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + this.palette = result; } /// - protected override void FirstPass(ImageFrame source, int width, int height) - { - this.Build3DHistogram(source, width, height); - this.Get3DMoments(source.MemoryAllocator); - this.BuildCube(); - } + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// - protected override void SecondPass(ImageFrame source, Span output, ReadOnlySpan palette, int width, int height) + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - // Load up the values for the first pixel. We can use these to speed up the second - // pass of the algorithm by avoiding transforming rows of identical color. - TPixel sourcePixel = source[0, 0]; - TPixel previousPixel = sourcePixel; - byte pixelValue = this.QuantizePixel(ref sourcePixel); - TPixel transformedPixel = palette[pixelValue]; - - for (int y = 0; y < height; y++) + if (this.isDithering) { - Span row = source.GetPixelRowSpan(y); - - // And loop through each column - for (int x = 0; x < width; x++) - { - // Get the pixel. - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - // Quantize the pixel - pixelValue = this.QuantizePixel(ref sourcePixel); + return (byte)this.pixelMap.GetClosestColor(color, out match); + } - // And setup the previous pointer - previousPixel = sourcePixel; + Rgba32 rgba = default; + color.ToRgba32(ref rgba); - if (this.Dither) - { - transformedPixel = palette[pixelValue]; - } - } + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); - if (this.Dither) - { - // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); - } + ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + match = Unsafe.Add(ref paletteRef, index); + return index; + } - output[(y * source.Width) + x] = pixelValue; - } + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.momentsOwner?.Dispose(); + this.tagsOwner?.Dispose(); + this.paletteOwner?.Dispose(); + this.momentsOwner = null; + this.tagsOwner = null; + this.paletteOwner = null; } } @@ -276,7 +195,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The blue value. /// The alpha value. /// The index. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static int GetPaletteIndex(int r, int g, int b, int a) { return (r << ((IndexBits * 2) + IndexAlphaBits)) @@ -293,26 +212,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Computes sum over a box of any given statistic. /// /// The cube. - /// The moment. + /// The moment. /// The result. - private static float Volume(ref Box cube, Span moment) + private static Moment Volume(ref Box cube, ReadOnlySpan moments) { - return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; } /// @@ -320,55 +239,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The cube. /// The direction. - /// The moment. + /// The moment. /// The result. - private static long Bottom(ref Box cube, int direction, Span moment) + private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan moments) { switch (direction) { // Red case 3: - return -moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Green case 2: - return -moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Blue case 1: - return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Alpha case 0: - return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -381,55 +300,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The cube. /// The direction. /// The position. - /// The moment. + /// The moment. /// The result. - private static long Top(ref Box cube, int direction, int position, Span moment) + private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpan moments) { switch (direction) { // Red case 3: - return moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)]; + return moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)]; // Green case 2: - return moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)]; + return moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)]; // Blue case 1: - return moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)]; + return moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)]; // Alpha case 0: - return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)]; + return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -440,49 +359,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// /// The source data. - /// The width in pixels of the image. - /// The height in pixels of the image. - private void Build3DHistogram(ImageFrame source, int width, int height) + /// The bounds within the source image to quantize. + private void Build3DHistogram(ImageFrame source, Rectangle bounds) { - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); + Span momentSpan = this.momentsOwner.GetSpan(); // Build up the 3-D color histogram - // Loop through each row - using (IMemoryOwner rgbaBuffer = source.MemoryAllocator.Allocate(source.Width)) - { - for (int y = 0; y < height; y++) - { - Span row = source.GetPixelRowSpan(y); - Span rgbaSpan = rgbaBuffer.GetSpan(); - PixelOperations.Instance.ToRgba32(source.Configuration, row, rgbaSpan); - ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan); + using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); - // And loop through each column - for (int x = 0; x < width; x++) - { - ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x); - - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + for (int x = 0; x < bufferSpan.Length; x++) + { + Rgba32 rgba = bufferSpan[x]; - vwtSpan[index]++; - vmrSpan[index] += rgba.R; - vmgSpan[index] += rgba.G; - vmbSpan[index] += rgba.B; - vmaSpan[index] += rgba.A; + int r = (rgba.R >> (8 - IndexBits)) + 1; + int g = (rgba.G >> (8 - IndexBits)) + 1; + int b = (rgba.B >> (8 - IndexBits)) + 1; + int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; - var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); - m2Span[index] += Vector4.Dot(vector, vector); - } + momentSpan[GetPaletteIndex(r, g, b, a)] += rgba; } } } @@ -490,106 +390,41 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// - /// The memory allocator used for allocating buffers. - private void Get3DMoments(MemoryAllocator memoryAllocator) + /// The memory allocator used for allocating buffers. + private void Get3DMoments(MemoryAllocator allocator) { - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); - - using (IMemoryOwner volume = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeR = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeG = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeB = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeA = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volume2 = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner area = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaR = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaG = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaB = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaA = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner area2 = memoryAllocator.Allocate(IndexAlphaCount)) + using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); + using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); + + Span momentSpan = this.momentsOwner.GetSpan(); + Span volumeSpan = volume.GetSpan(); + Span areaSpan = area.GetSpan(); + int baseIndex = GetPaletteIndex(1, 0, 0, 0); + + for (int r = 1; r < IndexCount; r++) { - Span volumeSpan = volume.GetSpan(); - Span volumeRSpan = volumeR.GetSpan(); - Span volumeGSpan = volumeG.GetSpan(); - Span volumeBSpan = volumeB.GetSpan(); - Span volumeASpan = volumeA.GetSpan(); - Span volume2Span = volume2.GetSpan(); - - Span areaSpan = area.GetSpan(); - Span areaRSpan = areaR.GetSpan(); - Span areaGSpan = areaG.GetSpan(); - Span areaBSpan = areaB.GetSpan(); - Span areaASpan = areaA.GetSpan(); - Span area2Span = area2.GetSpan(); - - for (int r = 1; r < IndexCount; r++) + volumeSpan.Clear(); + + for (int g = 1; g < IndexCount; g++) { - volume.Clear(); - volumeR.Clear(); - volumeG.Clear(); - volumeB.Clear(); - volumeA.Clear(); - volume2.Clear(); - - for (int g = 1; g < IndexCount; g++) + areaSpan.Clear(); + + for (int b = 1; b < IndexCount; b++) { - area.Clear(); - areaR.Clear(); - areaG.Clear(); - areaB.Clear(); - areaA.Clear(); - area2.Clear(); - - for (int b = 1; b < IndexCount; b++) + Moment line = default; + + for (int a = 1; a < IndexAlphaCount; a++) { - long line = 0; - long lineR = 0; - long lineG = 0; - long lineB = 0; - long lineA = 0; - double line2 = 0; - - for (int a = 1; a < IndexAlphaCount; a++) - { - int ind1 = GetPaletteIndex(r, g, b, a); - - line += vwtSpan[ind1]; - lineR += vmrSpan[ind1]; - lineG += vmgSpan[ind1]; - lineB += vmbSpan[ind1]; - lineA += vmaSpan[ind1]; - line2 += m2Span[ind1]; - - areaSpan[a] += line; - areaRSpan[a] += lineR; - areaGSpan[a] += lineG; - areaBSpan[a] += lineB; - areaASpan[a] += lineA; - area2Span[a] += line2; - - int inv = (b * IndexAlphaCount) + a; - - volumeSpan[inv] += areaSpan[a]; - volumeRSpan[inv] += areaRSpan[a]; - volumeGSpan[inv] += areaGSpan[a]; - volumeBSpan[inv] += areaBSpan[a]; - volumeASpan[inv] += areaASpan[a]; - volume2Span[inv] += area2Span[a]; - - int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); - - vwtSpan[ind1] = vwtSpan[ind2] + volumeSpan[inv]; - vmrSpan[ind1] = vmrSpan[ind2] + volumeRSpan[inv]; - vmgSpan[ind1] = vmgSpan[ind2] + volumeGSpan[inv]; - vmbSpan[ind1] = vmbSpan[ind2] + volumeBSpan[inv]; - vmaSpan[ind1] = vmaSpan[ind2] + volumeASpan[inv]; - m2Span[ind1] = m2Span[ind2] + volume2Span[inv]; - } + int ind1 = GetPaletteIndex(r, g, b, a); + line += momentSpan[ind1]; + + areaSpan[a] += line; + + int inv = (b * IndexAlphaCount) + a; + volumeSpan[inv] += areaSpan[a]; + + int ind2 = ind1 - baseIndex; + momentSpan[ind1] = momentSpan[ind2] + volumeSpan[inv]; } } } @@ -603,33 +438,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private double Variance(ref Box cube) { - float dr = Volume(ref cube, this.vmr.GetSpan()); - float dg = Volume(ref cube, this.vmg.GetSpan()); - float db = Volume(ref cube, this.vmb.GetSpan()); - float da = Volume(ref cube, this.vma.GetSpan()); - - Span m2Span = this.m2.GetSpan(); - - double moment = - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; - - var vector = new Vector4(dr, dg, db, da); - return moment - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.GetSpan())); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + + Moment volume = Volume(ref cube, momentSpan); + Moment variance = + momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + var vector = new Vector4(volume.R, volume.G, volume.B, volume.A); + return variance.Moment2 - (Vector4.Dot(vector, vector) / volume.Weight); } /// @@ -644,60 +475,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The first position. /// The last position. /// The cutting point. - /// The whole red. - /// The whole green. - /// The whole blue. - /// The whole alpha. - /// The whole weight. + /// The whole moment. /// The . - private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) + private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) { - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - - long baseR = Bottom(ref cube, direction, vmrSpan); - long baseG = Bottom(ref cube, direction, vmgSpan); - long baseB = Bottom(ref cube, direction, vmbSpan); - long baseA = Bottom(ref cube, direction, vmaSpan); - long baseW = Bottom(ref cube, direction, vwtSpan); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + Moment bottom = Bottom(ref cube, direction, momentSpan); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(ref cube, direction, i, vmrSpan); - float halfG = baseG + Top(ref cube, direction, i, vmgSpan); - float halfB = baseB + Top(ref cube, direction, i, vmbSpan); - float halfA = baseA + Top(ref cube, direction, i, vmaSpan); - float halfW = baseW + Top(ref cube, direction, i, vwtSpan); + Moment half = bottom + Top(ref cube, direction, i, momentSpan); - if (MathF.Abs(halfW) < Constants.Epsilon) + if (half.Weight == 0) { continue; } - var vector = new Vector4(halfR, halfG, halfB, halfA); - float temp = Vector4.Dot(vector, vector) / halfW; + var vector = new Vector4(half.R, half.G, half.B, half.A); + float temp = Vector4.Dot(vector, vector) / half.Weight; - halfW = wholeW - halfW; + half = whole - half; - if (MathF.Abs(halfW) < Constants.Epsilon) + if (half.Weight == 0) { continue; } - halfR = wholeR - halfR; - halfG = wholeG - halfG; - halfB = wholeB - halfB; - halfA = wholeA - halfA; - - vector = new Vector4(halfR, halfG, halfB, halfA); - - temp += Vector4.Dot(vector, vector) / halfW; + vector = new Vector4(half.R, half.G, half.B, half.A); + temp += Vector4.Dot(vector, vector) / half.Weight; if (temp > max) { @@ -717,33 +525,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Returns a value indicating whether the box has been split. private bool Cut(ref Box set1, ref Box set2) { - float wholeR = Volume(ref set1, this.vmr.GetSpan()); - float wholeG = Volume(ref set1, this.vmg.GetSpan()); - float wholeB = Volume(ref set1, this.vmb.GetSpan()); - float wholeA = Volume(ref set1, this.vma.GetSpan()); - float wholeW = Volume(ref set1, this.vwt.GetSpan()); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + Moment whole = Volume(ref set1, momentSpan); - float maxr = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxg = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxb = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxa = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole); + float maxG = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutG, whole); + float maxB = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutB, whole); + float maxA = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cutA, whole); int dir; - if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) + if ((maxR >= maxG) && (maxR >= maxB) && (maxR >= maxA)) { dir = 3; - if (cutr < 0) + if (cutR < 0) { return false; } } - else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + else if ((maxG >= maxR) && (maxG >= maxB) && (maxG >= maxA)) { dir = 2; } - else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + else if ((maxB >= maxR) && (maxB >= maxG) && (maxB >= maxA)) { dir = 1; } @@ -761,7 +566,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { // Red case 3: - set2.RMin = set1.RMax = cutr; + set2.RMin = set1.RMax = cutR; set2.GMin = set1.GMin; set2.BMin = set1.BMin; set2.AMin = set1.AMin; @@ -769,7 +574,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Green case 2: - set2.GMin = set1.GMax = cutg; + set2.GMin = set1.GMax = cutG; set2.RMin = set1.RMin; set2.BMin = set1.BMin; set2.AMin = set1.AMin; @@ -777,7 +582,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Blue case 1: - set2.BMin = set1.BMax = cutb; + set2.BMin = set1.BMax = cutB; set2.RMin = set1.RMin; set2.GMin = set1.GMin; set2.AMin = set1.AMin; @@ -785,7 +590,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Alpha case 0: - set2.AMin = set1.AMax = cuta; + set2.AMin = set1.AMax = cutA; set2.RMin = set1.RMin; set2.GMin = set1.GMin; set2.BMin = set1.BMin; @@ -805,7 +610,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// A label. private void Mark(ref Box cube, byte label) { - Span tagSpan = this.tag.GetSpan(); + Span tagSpan = this.tagsOwner.GetSpan(); for (int r = cube.RMin + 1; r <= cube.RMax; r++) { @@ -827,8 +632,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private void BuildCube() { - this.colorCube = new Box[this.colors]; - var vv = new double[this.colors]; + // Store the volume variance. + using IMemoryOwner vvOwner = this.Configuration.MemoryAllocator.Allocate(this.maxColors); + Span vv = vvOwner.GetSpan(); ref Box cube = ref this.colorCube[0]; cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; @@ -837,14 +643,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int next = 0; - for (int i = 1; i < this.colors; i++) + for (int i = 1; i < this.maxColors; i++) { ref Box nextCube = ref this.colorCube[next]; ref Box currentCube = ref this.colorCube[i]; if (this.Cut(ref nextCube, ref currentCube)) { - vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F; - vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0F; + vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0D; + vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0D; } else { @@ -866,41 +672,98 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (temp <= 0D) { - this.colors = i + 1; + this.maxColors = i + 1; break; } } } - /// - /// Process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(ref TPixel pixel) + private struct Moment { - if (this.Dither) + /// + /// Moment of r*P(c). + /// + public long R; + + /// + /// Moment of g*P(c). + /// + public long G; + + /// + /// Moment of b*P(c). + /// + public long B; + + /// + /// Moment of a*P(c). + /// + public long A; + + /// + /// Moment of P(c). + /// + public long Weight; + + /// + /// Moment of c^2*P(c). + /// + public double Moment2; + + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator +(Moment x, Moment y) { - // The colors have changed so we need to use Euclidean distance calculation to - // find the closest value. - return this.GetClosestPixel(ref pixel); + x.R += y.R; + x.G += y.G; + x.B += y.B; + x.A += y.A; + x.Weight += y.Weight; + x.Moment2 += y.Moment2; + return x; } - // Expected order r->g->b->a - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator -(Moment x, Moment y) + { + x.R -= y.R; + x.G -= y.G; + x.B -= y.B; + x.A -= y.A; + x.Weight -= y.Weight; + x.Moment2 -= y.Moment2; + return x; + } - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator -(Moment x) + { + x.R = -x.R; + x.G = -x.G; + x.B = -x.B; + x.A = -x.A; + x.Weight = -x.Weight; + x.Moment2 = -x.Moment2; + return x; + } - Span tagSpan = this.tag.GetSpan(); + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator +(Moment x, Rgba32 y) + { + x.R += y.R; + x.G += y.G; + x.B += y.B; + x.A += y.A; + x.Weight++; + + var vector = new Vector4(y.R, y.G, y.B, y.A); + x.Moment2 += Vector4.Dot(vector, vector); + + return x; + } - return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 Normalize() + => new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F; } /// @@ -954,10 +817,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public int Volume; /// - public override bool Equals(object obj) => obj is Box box && this.Equals(box); + public readonly override bool Equals(object obj) + => obj is Box box + && this.Equals(box); /// - public bool Equals(Box other) => + public readonly bool Equals(Box other) => this.RMin == other.RMin && this.RMax == other.RMax && this.GMin == other.GMin @@ -969,7 +834,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization && this.Volume == other.Volume; /// - public override int GetHashCode() + public readonly override int GetHashCode() { HashCode hash = default; hash.Add(this.RMin); diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index b80cedeb39..d2e33aa1f1 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -1,91 +1,47 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class WuQuantizer : IQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WuQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// - /// The maximum number of colors to hold in the color palette - public WuQuantizer(int maxColors) - : this(GetDiffuser(true), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image - public WuQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffusion algorithm, if any, to apply to the output image - public WuQuantizer(IErrorDiffuser diffuser) - : this(diffuser, QuantizerConstants.MaxColors) + public WuQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - /// The maximum number of colors to hold in the color palette - public WuQuantizer(IErrorDiffuser diffuser, int maxColors) + /// The quantizer options defining quantization rules. + public WuQuantizer(QuantizerOptions options) { - this.Diffuser = diffuser; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the maximum number of colors to hold in the color palette. - /// - public int MaxColors { get; } + public QuantizerOptions Options { get; } - /// /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - return new WuFrameQuantizer(configuration.MemoryAllocator, this); - } + where TPixel : unmanaged, IPixel + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new WuFrameQuantizer(configuration.MemoryAllocator, this, maxColors); - } - - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new WuFrameQuantizer(configuration, options); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs deleted file mode 100644 index 6ca844fae6..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Defines an affine transformation applicable on an . - /// - public class AffineTransformProcessor : CloningImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix. - /// The sampler to perform the transform operation. - /// The target dimensions. - public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) - { - Guard.NotNull(sampler, nameof(sampler)); - this.Sampler = sampler; - this.TransformMatrix = matrix; - this.TargetDimensions = targetDimensions; - } - - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets the matrix used to supply the affine transform. - /// - public Matrix3x2 TransformMatrix { get; } - - /// - /// Gets the target dimensions to constrain the transformed image to. - /// - public Size TargetDimensions { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - => new AffineTransformProcessor(this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs deleted file mode 100644 index 97b8b009b5..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Provides the base methods to perform affine transforms on an image. - /// - /// The pixel format. - internal class AffineTransformProcessor : TransformProcessor - where TPixel : struct, IPixel - { - private Size targetSize; - private Matrix3x2 transformMatrix; - private readonly IResampler resampler; - - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AffineTransformProcessor(AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.targetSize = definition.TargetDimensions; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetTargetSize() => this.targetSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Handle transforms that result in output identical to the original. - if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.targetSize.Width; - Rectangle sourceBounds = this.SourceRectangle; - var targetBounds = new Rectangle(Point.Empty, this.targetSize); - Configuration configuration = this.Configuration; - - // Convert from screen to world space. - Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix); - - if (this.resampler is NearestNeighborResampler) - { - ParallelHelper.IterateRows( - targetBounds, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - var point = Point.Transform(new Point(x, y), matrix); - if (sourceBounds.Contains(point.X, point.Y)) - { - destRow[x] = source[point.X, point.Y]; - } - } - } - }); - - return; - } - - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - - try - { - ParallelHelper.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref kernel.GetYStartReference(y); - ref float xSpanRef = ref kernel.GetXStartReference(y); - - for (int x = 0; x < width; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), matrix); - kernel.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - source.PixelBuffer, - vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive( - configuration, - vectorSpan, - targetRowSpan); - } - }); - } - finally - { - kernel.Dispose(); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs deleted file mode 100644 index eef7643da3..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. - /// - public sealed class AutoOrientProcessor : IImageProcessor - { - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new AutoOrientProcessor(source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs deleted file mode 100644 index b9952ac8fe..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. - /// - /// The pixel format. - internal class AutoOrientProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AutoOrientProcessor(Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - } - - /// - protected override void BeforeImageApply() - { - OrientationMode orientation = GetExifOrientation(this.Source); - Size size = this.SourceRectangle.Size; - switch (orientation) - { - case OrientationMode.TopRight: - new FlipProcessor(FlipMode.Horizontal).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.BottomRight: - new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.BottomLeft: - new FlipProcessor(FlipMode.Vertical).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.LeftTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Source, this.SourceRectangle); - new FlipProcessor(FlipMode.Horizontal).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.RightTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.RightBottom: - new FlipProcessor(FlipMode.Vertical).Execute(this.Source, this.SourceRectangle); - new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.LeftBottom: - new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Source, this.SourceRectangle); - break; - - case OrientationMode.Unknown: - case OrientationMode.TopLeft: - default: - break; - } - - base.BeforeImageApply(); - } - - /// - protected override void OnFrameApply(ImageFrame sourceBase) - { - // All processing happens at the image level within BeforeImageApply(); - } - - /// - /// Returns the current EXIF orientation - /// - /// The image to auto rotate. - /// The - private static OrientationMode GetExifOrientation(Image source) - { - if (source.Metadata.ExifProfile is null) - { - return OrientationMode.Unknown; - } - - ExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); - if (value is null) - { - return OrientationMode.Unknown; - } - - OrientationMode orientation; - if (value.DataType == ExifDataType.Short) - { - orientation = (OrientationMode)value.Value; - } - else - { - orientation = (OrientationMode)Convert.ToUInt16(value.Value); - source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); - } - - source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); - - return orientation; - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 245a542084..9aa21e4dcd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -32,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Rectangle CropRectangle { get; } /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - => new CropProcessor(this, source, sourceRectangle); + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new CropProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 1bbdd0a161..a366fd51de 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -14,22 +14,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class CropProcessor : TransformProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - private Rectangle cropRectangle; + private readonly Rectangle cropRectangle; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The . /// The source for the current processor instance. /// The source area to process for the current processor instance. - public CropProcessor(CropProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public CropProcessor(Configuration configuration, CropProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) => this.cropRectangle = definition.CropRectangle; /// - protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); + protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) @@ -40,30 +41,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms && this.SourceRectangle == this.cropRectangle) { // the cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } Rectangle bounds = this.cropRectangle; // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings - = this.Configuration - .GetParallelSettings() - .MultiplyMinimumPixelsPerTask(4); + ParallelExecutionSettings parallelSettings = + ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - ParallelHelper.IterateRows( + var operation = new RowOperation(bounds, source, destination); + + ParallelRowIterator.IterateRows( bounds, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); - Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); - sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); - } - }); + in parallelSettings, + in operation); + } + + /// + /// A implementing the processor logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The source for the current instance. + /// The destination for the current instance. + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) + { + this.bounds = bounds; + this.source = source; + this.destination = destination; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs new file mode 100644 index 0000000000..4d46540bcc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Represents an error that occurs during a transform operation. + /// + public sealed class DegenerateTransformException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public DegenerateTransformException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public DegenerateTransformException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is + /// the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. + public DegenerateTransformException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index 22eecb598e..b2a50a9881 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -38,10 +37,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public float Threshold { get; } /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new EntropyCropProcessor(this, source, sourceRectangle); - } + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EntropyCropProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs index 2b900ee360..8c91e19531 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -14,18 +13,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class EntropyCropProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly EntropyCropProcessor definition; /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The . /// The source for the current processor instance. /// The source area to process for the current processor instance. - public EntropyCropProcessor(EntropyCropProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public EntropyCropProcessor(Configuration configuration, EntropyCropProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { this.definition = definition; } @@ -42,16 +42,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration = this.Source.GetConfiguration(); // Detect the edges. - new SobelProcessor(false).Execute(temp, this.SourceRectangle); + new SobelProcessor(false).Execute(this.Configuration, temp, this.SourceRectangle); // Apply threshold binarization filter. - new BinaryThresholdProcessor(this.definition.Threshold).Execute(temp, this.SourceRectangle); + new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle); // Search for the first white pixels rectangle = ImageMaths.GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); } - new CropProcessor(rectangle, this.Source.Size()).Execute(this.Source, this.SourceRectangle); + new CropProcessor(rectangle, this.Source.Size()).Execute(this.Configuration, this.Source, this.SourceRectangle); base.BeforeImageApply(); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs deleted file mode 100644 index e2364e180f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Defines a flipping around the center point of the image. - /// - public sealed class FlipProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The used to perform flipping. - public FlipProcessor(FlipMode flipMode) - { - this.FlipMode = flipMode; - } - - /// - /// Gets the used to perform flipping. - /// - public FlipMode FlipMode { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - { - return new FlipProcessor(this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs deleted file mode 100644 index 9374af476b..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Provides methods that allow the flipping of an image around its center point. - /// - /// The pixel format. - internal class FlipProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly FlipProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public FlipProcessor(FlipProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - switch (this.definition.FlipMode) - { - // No default needed as we have already set the pixels. - case FlipMode.Vertical: - this.FlipX(source, this.Configuration); - break; - case FlipMode.Horizontal: - this.FlipY(source, this.Configuration); - break; - } - } - - /// - /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private void FlipX(ImageFrame source, Configuration configuration) - { - int height = source.Height; - - using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) - { - Span temp = tempBuffer.Memory.Span; - - for (int yTop = 0; yTop < height / 2; yTop++) - { - int yBottom = height - yTop - 1; - Span topRow = source.GetPixelRowSpan(yBottom); - Span bottomRow = source.GetPixelRowSpan(yTop); - topRow.CopyTo(temp); - bottomRow.CopyTo(topRow); - temp.CopyTo(bottomRow); - } - } - } - - /// - /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private void FlipY(ImageFrame source, Configuration configuration) - { - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - source.GetPixelRowSpan(y).Reverse(); - } - }); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index 6db03d5b41..55eebba4f7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -1,6 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -21,5 +23,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The /// float GetValue(float x); + + /// + /// Applies a transformation upon an image. + /// + /// The pixel format. + /// The transforming image processor. + void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs new file mode 100644 index 0000000000..02df8282f0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Implements an algorithm to alter the pixels of an image via resampling transforms. + /// + /// The pixel format. + public interface IResamplingTransformImageProcessor : IImageProcessor + where TPixel : unmanaged, IPixel + { + /// + /// Applies a resampling transform with the given sampler. + /// + /// The type of sampler. + /// The sampler to use. + void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler; + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs new file mode 100644 index 0000000000..fec41dbffe --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Defines an affine transformation applicable on an . + /// + public class AffineTransformProcessor : CloningImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix. + /// The sampler to perform the transform operation. + /// The target dimensions. + public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) + { + Guard.NotNull(sampler, nameof(sampler)); + Guard.MustBeValueType(sampler, nameof(sampler)); + + this.Sampler = sampler; + this.TransformMatrix = matrix; + this.DestinationSize = targetDimensions; + } + + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the matrix used to supply the affine transform. + /// + public Matrix3x2 TransformMatrix { get; } + + /// + /// Gets the destination size to constrain the transformed image to. + /// + public Size DestinationSize { get; } + + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new AffineTransformProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs new file mode 100644 index 0000000000..a3c8f71082 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform affine transforms on an image. + /// + /// The pixel format. + internal class AffineTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly Size destinationSize; + private readonly Matrix3x2 transformMatrix; + private readonly IResampler resampler; + private ImageFrame source; + private ImageFrame destination; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } + + protected override Size GetDestinationSize() => this.destinationSize; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + { + Configuration configuration = this.Configuration; + ImageFrame source = this.source; + ImageFrame destination = this.destination; + Matrix3x2 matrix = this.transformMatrix; + + // Handle transforms that result in output identical to the original. + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } + + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNAffineOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + + var operation = new AffineOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNAffineOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix3x2 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNAffineOperation( + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = source.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + + private readonly struct AffineOperation : IRowOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Buffer2D yKernelBuffer; + private readonly Buffer2D xKernelBuffer; + private readonly TResampler sampler; + private readonly Matrix3x2 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public AffineOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix3x2 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Buffer2D sourceBuffer = this.source.PixelBuffer; + + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); + + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs new file mode 100644 index 0000000000..534832d13f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. + /// + public sealed class AutoOrientProcessor : IImageProcessor + { + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new AutoOrientProcessor(configuration, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs new file mode 100644 index 0000000000..be1388dce4 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. + /// + /// The pixel format. + internal class AutoOrientProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AutoOrientProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + } + + /// + protected override void BeforeImageApply() + { + OrientationMode orientation = GetExifOrientation(this.Source); + Size size = this.SourceRectangle.Size; + switch (orientation) + { + case OrientationMode.TopRight: + new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.BottomRight: + new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.BottomLeft: + new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.LeftTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.RightTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.RightBottom: + new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); + new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.LeftBottom: + new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case OrientationMode.Unknown: + case OrientationMode.TopLeft: + default: + break; + } + + base.BeforeImageApply(); + } + + /// + protected override void OnFrameApply(ImageFrame sourceBase) + { + // All processing happens at the image level within BeforeImageApply(); + } + + /// + /// Returns the current EXIF orientation + /// + /// The image to auto rotate. + /// The + private static OrientationMode GetExifOrientation(Image source) + { + if (source.Metadata.ExifProfile is null) + { + return OrientationMode.Unknown; + } + + IExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); + if (value is null) + { + return OrientationMode.Unknown; + } + + OrientationMode orientation; + if (value.DataType == ExifDataType.Short) + { + orientation = (OrientationMode)value.Value; + } + else + { + orientation = (OrientationMode)Convert.ToUInt16(value.Value); + source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); + } + + source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); + + return orientation; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs new file mode 100644 index 0000000000..b154aba88a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Defines a flipping around the center point of the image. + /// + public sealed class FlipProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The used to perform flipping. + public FlipProcessor(FlipMode flipMode) => this.FlipMode = flipMode; + + /// + /// Gets the used to perform flipping. + /// + public FlipMode FlipMode { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new FlipProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs new file mode 100644 index 0000000000..470eafcd8d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the flipping of an image around its center point. + /// + /// The pixel format. + internal class FlipProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly FlipProcessor definition; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The . + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public FlipProcessor(Configuration configuration, FlipProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.definition = definition; + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + switch (this.definition.FlipMode) + { + // No default needed as we have already set the pixels. + case FlipMode.Vertical: + this.FlipX(source, this.Configuration); + break; + case FlipMode.Horizontal: + this.FlipY(source, this.Configuration); + break; + } + } + + /// + /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private void FlipX(ImageFrame source, Configuration configuration) + { + int height = source.Height; + using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); + Span temp = tempBuffer.Memory.Span; + + for (int yTop = 0; yTop < height / 2; yTop++) + { + int yBottom = height - yTop - 1; + Span topRow = source.GetPixelRowSpan(yBottom); + Span bottomRow = source.GetPixelRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); + } + } + + /// + /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private void FlipY(ImageFrame source, Configuration configuration) + { + var operation = new RowOperation(source); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation(ImageFrame source) => this.source = source; + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) => this.source.GetPixelRowSpan(y).Reverse(); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs new file mode 100644 index 0000000000..0a00cf8e9b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Utility methods for affine and projective transforms. + /// + internal static class LinearTransformUtilities + { + [MethodImpl(InliningOptions.ShortMethod)] + internal static int GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) + where TResampler : struct, IResampler + { + double scale = sourceSize / destinationSize; + if (scale < 1) + { + scale = 1; + } + + return (int)Math.Ceiling(scale * sampler.Radius); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal static void Convolve( + in TResampler sampler, + Vector2 transformedPoint, + Buffer2D sourcePixels, + Span targetRow, + int column, + ref float yKernelSpanRef, + ref float xKernelSpanRef, + Vector2 radialExtents, + Vector4 maxSourceExtents) + where TResampler : struct, IResampler + where TPixel : unmanaged, IPixel + { + // Clamp sampling pixel radial extents to the source image edges + Vector2 minXY = transformedPoint - radialExtents; + Vector2 maxXY = transformedPoint + radialExtents; + + // left, top, right, bottom + var sourceExtents = new Vector4( + MathF.Ceiling(minXY.X), + MathF.Ceiling(minXY.Y), + MathF.Floor(maxXY.X), + MathF.Floor(maxXY.Y)); + + sourceExtents = Vector4Utilities.FastClamp(sourceExtents, Vector4.Zero, maxSourceExtents); + + int left = (int)sourceExtents.X; + int top = (int)sourceExtents.Y; + int right = (int)sourceExtents.Z; + int bottom = (int)sourceExtents.W; + + if (left == right || top == bottom) + { + return; + } + + CalculateWeights(in sampler, top, bottom, transformedPoint.Y, ref yKernelSpanRef); + CalculateWeights(in sampler, left, right, transformedPoint.X, ref xKernelSpanRef); + + Vector4 sum = Vector4.Zero; + for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) + { + float yWeight = Unsafe.Add(ref yKernelSpanRef, kernelY); + + for (int kernelX = 0, x = left; x <= right; x++, kernelX++) + { + float xWeight = Unsafe.Add(ref xKernelSpanRef, kernelX); + + // Values are first premultiplied to prevent darkening of edge pixels. + var current = sourcePixels[x, y].ToVector4(); + Vector4Utilities.Premultiply(ref current); + sum += current * xWeight * yWeight; + } + } + + // Reverse the premultiplication + Vector4Utilities.UnPremultiply(ref sum); + targetRow[column] = sum; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void CalculateWeights(in TResampler sampler, int min, int max, float point, ref float weightsRef) + where TResampler : struct, IResampler + { + float sum = 0; + for (int x = 0, i = min; i <= max; i++, x++) + { + float weight = sampler.GetValue(i - point); + sum += weight; + Unsafe.Add(ref weightsRef, x) = weight; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs new file mode 100644 index 0000000000..f716ba701e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Defines a projective transformation applicable to an . + /// + public sealed class ProjectiveTransformProcessor : CloningImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix. + /// The sampler to perform the transform operation. + /// The target dimensions. + public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) + { + Guard.NotNull(sampler, nameof(sampler)); + Guard.MustBeValueType(sampler, nameof(sampler)); + + this.Sampler = sampler; + this.TransformMatrix = matrix; + this.DestinationSize = targetDimensions; + } + + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the matrix used to supply the projective transform. + /// + public Matrix4x4 TransformMatrix { get; } + + /// + /// Gets the destination size to constrain the transformed image to. + /// + public Size DestinationSize { get; } + + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new ProjectiveTransformProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs new file mode 100644 index 0000000000..f348721d7a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform non-affine transforms on an image. + /// + /// The pixel format. + internal class ProjectiveTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly Size destinationSize; + private readonly IResampler resampler; + private readonly Matrix4x4 transformMatrix; + private ImageFrame source; + private ImageFrame destination; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } + + protected override Size GetDestinationSize() => this.destinationSize; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + { + Configuration configuration = this.Configuration; + ImageFrame source = this.source; + ImageFrame destination = this.destination; + Matrix4x4 matrix = this.transformMatrix; + + // Handle transforms that result in output identical to the original. + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } + + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNProjectiveOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + + var operation = new ProjectiveOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNProjectiveOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNProjectiveOperation( + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = source.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + + private readonly struct ProjectiveOperation : IRowOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Buffer2D yKernelBuffer; + private readonly Buffer2D xKernelBuffer; + private readonly TResampler sampler; + private readonly Matrix4x4 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ProjectiveOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix4x4 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Buffer2D sourceBuffer = this.source.PixelBuffer; + + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); + + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs new file mode 100644 index 0000000000..b53e7b5c05 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Defines a rotation applicable to an . + /// + public sealed class RotateProcessor : AffineTransformProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The angle of rotation in degrees. + /// The source image size + public RotateProcessor(float degrees, Size sourceSize) + : this(degrees, KnownResamplers.Bicubic, sourceSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The angle of rotation in degrees. + /// The sampler to perform the rotating operation. + /// The source image size + public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) + : this( + TransformUtilities.CreateRotationMatrixDegrees(degrees, sourceSize), + sampler, + sourceSize) + => this.Degrees = degrees; + + // Helper constructor + private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) + : base(rotationMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, rotationMatrix)) + { + } + + /// + /// Gets the angle of rotation in degrees. + /// + public float Degrees { get; } + + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new RotateProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs new file mode 100644 index 0000000000..43f67f7914 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -0,0 +1,286 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the rotating of images. + /// + /// The pixel format. + internal class RotateProcessor : AffineTransformProcessor + where TPixel : unmanaged, IPixel + { + private readonly float degrees; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public RotateProcessor(Configuration configuration, RotateProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, definition, source, sourceRectangle) + => this.degrees = definition.Degrees; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + if (this.OptimizedApply(source, destination, this.Configuration)) + { + return; + } + + base.OnFrameApply(source, destination); + } + + /// + protected override void AfterImageApply(Image destination) + { + ExifProfile profile = destination.Metadata.ExifProfile; + if (profile is null) + { + return; + } + + if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) + { + // No need to do anything so return. + return; + } + + profile.RemoveValue(ExifTag.Orientation); + + base.AfterImageApply(destination); + } + + /// + /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range + /// + /// The angle of rotation in degrees. + /// The . + private static float WrapDegrees(float degrees) + { + degrees %= 360; + + while (degrees < 0) + { + degrees += 360; + } + + return degrees; + } + + /// + /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. + /// + /// The source image. + /// The destination image. + /// The configuration. + /// + /// The + /// + private bool OptimizedApply( + ImageFrame source, + ImageFrame destination, + Configuration configuration) + { + // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. + float degrees = WrapDegrees(this.degrees); + + if (MathF.Abs(degrees) < Constants.Epsilon) + { + // The destination will be blank here so copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return true; + } + + if (MathF.Abs(degrees - 90) < Constants.Epsilon) + { + this.Rotate90(source, destination, configuration); + return true; + } + + if (MathF.Abs(degrees - 180) < Constants.Epsilon) + { + this.Rotate180(source, destination, configuration); + return true; + } + + if (MathF.Abs(degrees - 270) < Constants.Epsilon) + { + this.Rotate270(source, destination, configuration); + return true; + } + + return false; + } + + /// + /// Rotates the image 180 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate180RowOperation(source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + /// + /// Rotates the image 270 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRowIntervals( + configuration, + source.Bounds(), + in operation); + } + + /// + /// Rotates the image 90 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + private readonly struct Rotate180RowOperation : IRowOperation + { + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate180RowOperation( + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + + for (int x = 0; x < this.width; x++) + { + targetRow[this.width - x - 1] = sourceRow[x]; + } + } + } + + private readonly struct Rotate270RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate270RowIntervalOperation( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + for (int x = 0; x < this.width; x++) + { + int newX = this.height - y - 1; + newX = this.height - newX - 1; + int newY = this.width - x - 1; + + if (this.bounds.Contains(newX, newY)) + { + this.destination[newX, newY] = sourceRow[x]; + } + } + } + } + } + + private readonly struct Rotate90RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate90RowOperation( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) + { + if (this.bounds.Contains(newX, x)) + { + this.destination[newX, x] = sourceRow[x]; + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs new file mode 100644 index 0000000000..1bcfa5fd28 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Defines a skew transformation applicable to an . + /// + public sealed class SkewProcessor : AffineTransformProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The source image size + public SkewProcessor(float degreesX, float degreesY, Size sourceSize) + : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The sampler to perform the skew operation. + /// The source image size + public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) + : this( + TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), + sampler, + sourceSize) + { + this.DegreesX = degreesX; + this.DegreesY = degreesY; + } + + // Helper constructor: + private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) + : base(skewMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, skewMatrix)) + { + } + + /// + /// Gets the angle of rotation along the x-axis in degrees. + /// + public float DegreesX { get; } + + /// + /// Gets the angle of rotation along the y-axis in degrees. + /// + public float DegreesY { get; } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs deleted file mode 100644 index d91db9a72b..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Defines a projective transformation applicable to an . - /// - public sealed class ProjectiveTransformProcessor : CloningImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix. - /// The sampler to perform the transform operation. - /// The target dimensions. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) - { - Guard.NotNull(sampler, nameof(sampler)); - this.Sampler = sampler; - this.TransformMatrix = matrix; - this.TargetDimensions = targetDimensions; - } - - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets the matrix used to supply the projective transform. - /// - public Matrix4x4 TransformMatrix { get; } - - /// - /// Gets the target dimensions to constrain the transformed image to. - /// - public Size TargetDimensions { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - => new ProjectiveTransformProcessor(this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs deleted file mode 100644 index 68bfd817e5..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Provides the base methods to perform non-affine transforms on an image. - /// - /// The pixel format. - internal class ProjectiveTransformProcessor : TransformProcessor - where TPixel : struct, IPixel - { - private Size targetSize; - private readonly IResampler resampler; - private Matrix4x4 transformMatrix; - - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) - { - this.targetSize = definition.TargetDimensions; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetTargetSize() => this.targetSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Handle transforms that result in output identical to the original. - if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.targetSize.Width; - Rectangle sourceBounds = this.SourceRectangle; - var targetBounds = new Rectangle(Point.Empty, this.targetSize); - Configuration configuration = this.Configuration; - - // Convert from screen to world space. - Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix); - - if (this.resampler is NearestNeighborResampler) - { - ParallelHelper.IterateRows( - targetBounds, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (sourceBounds.Contains(px, py)) - { - destRow[x] = source[px, py]; - } - } - } - }); - - return; - } - - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - - try - { - ParallelHelper.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref kernel.GetYStartReference(y); - ref float xSpanRef = ref kernel.GetXStartReference(y); - - for (int x = 0; x < width; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - kernel.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - source.PixelBuffer, - vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive( - configuration, - vectorSpan, - targetRowSpan); - } - }); - } - finally - { - kernel.Dispose(); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index 199563bc7e..b0a79766f2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. /// - public class BicubicResampler : IResampler + public readonly struct BicubicResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -21,21 +25,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms x = -x; } - float result = 0; - // Given the coefficient "a" as -0.5F. if (x <= 1F) { // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; - result = (((1.5F * x) - 2.5F) * x * x) + 1; + return (((1.5F * x) - 2.5F) * x * x) + 1; } else if (x < 2F) { // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); - result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; + return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; } - return result; + return 0; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index 0667226d9c..590d292e0b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the box algorithm. Similar to nearest neighbor when upscaling. /// When downscaling the pixels will average, merging together. /// - public class BoxResampler : IResampler + public readonly struct BoxResampler : IResampler { /// public float Radius => 0.5F; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x > -0.5F && x <= 0.5F) @@ -22,5 +26,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs deleted file mode 100644 index 8995d2d8a8..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. - /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large - /// scale image enlargements that a 'Lagrange' filter can produce. - /// - /// - public class CatmullRomResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0; - const float C = 0.5F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs new file mode 100644 index 0000000000..8cdfcd8820 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Cubic filters contain a collection of different filters of varying B-Spline and + /// Cardinal values. With these two values you can generate any smoothly fitting + /// (continuious first derivative) piece-wise cubic filter. + /// + /// + /// + public readonly struct CubicResampler : IResampler + { + private readonly float bspline; + private readonly float cardinal; + + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + public static CubicResampler CatmullRom = new CubicResampler(2, 0, .5F); + + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + public static CubicResampler Hermite = new CubicResampler(2, 0, 0); + + /// + /// The function implements the Mitchell-Netravali algorithm as described on + /// Wikipedia + /// + public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F); + + /// + /// The function implements the Robidoux algorithm. + /// + /// + public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F); + + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F); + + /// + /// The function implements the spline algorithm. + /// + /// + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler Spline = new CubicResampler(2, 1, 0); + + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + /// The B-Spline value. + /// The Cardinal cubic value. + public CubicResampler(float radius, float bspline, float cardinal) + { + this.Radius = radius; + this.bspline = bspline; + this.cardinal = cardinal; + } + + /// + public float Radius { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + float b = this.bspline; + float c = this.cardinal; + + if (x < 0F) + { + x = -x; + } + + float temp = x * x; + if (x < 1F) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6F; + } + + if (x < 2F) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6F; + } + + return 0F; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs deleted file mode 100644 index 18c3fda7c0..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The Hermite filter is type of smoothed triangular interpolation Filter, - /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. - /// - /// - public class HermiteResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs deleted file mode 100644 index 2294696de4..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 2 pixels. - /// - public class Lanczos2Resampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 2F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs deleted file mode 100644 index 95fb206a96..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 3 pixels. - /// - public class Lanczos3Resampler : IResampler - { - /// - public float Radius => 3; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 3F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs deleted file mode 100644 index c99ed1e855..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 5 pixels. - /// - public class Lanczos5Resampler : IResampler - { - /// - public float Radius => 5; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 5F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs deleted file mode 100644 index 4efdb882b0..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 8 pixels. - /// - public class Lanczos8Resampler : IResampler - { - /// - public float Radius => 8; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 8F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs new file mode 100644 index 0000000000..7eb6d111e0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia. + /// + public readonly struct LanczosResampler : IResampler + { + /// + /// Implements the Lanczos kernel algorithm with a radius of 2. + /// + public static LanczosResampler Lanczos2 = new LanczosResampler(2); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 3. + /// + public static LanczosResampler Lanczos3 = new LanczosResampler(3); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 5. + /// + public static LanczosResampler Lanczos5 = new LanczosResampler(5); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 8. + /// + public static LanczosResampler Lanczos8 = new LanczosResampler(8); + + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + public LanczosResampler(float radius) => this.Radius = radius; + + /// + public float Radius { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + float radius = this.Radius; + if (x < radius) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / radius); + } + + return 0F; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs deleted file mode 100644 index d4ba954f20..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the mitchell algorithm as described on - /// Wikipedia - /// - public class MitchellNetravaliResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.3333333F; - const float C = 0.3333333F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs index 1f12334f4f..9a78af82b4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,21 +1,28 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the nearest neighbor algorithm. This uses an unscaled filter /// which will select the closest pixel to the new pixels position. /// - public class NearestNeighborResampler : IResampler + public readonly struct NearestNeighborResampler : IResampler { /// public float Radius => 1; /// - public float GetValue(float x) - { - return x; - } + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) => x; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs deleted file mode 100644 index 51938566c8..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Robidoux algorithm. - /// - /// - public class RobidouxResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.37821575509399867F; - const float C = 0.31089212245300067F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs deleted file mode 100644 index 015b7f0af3..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public class RobidouxSharpResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.2620145123990142F; - const float C = 0.3689927438004929F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs deleted file mode 100644 index df6c2a338f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the spline algorithm. - /// - /// - public class SplineResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 1F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs index 57d1fa11dc..345e567902 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, /// so that one can calculate and assign appropriate intensity values to pixels. /// - public class TriangleResampler : IResampler + public readonly struct TriangleResampler : IResampler { /// public float Radius => 1; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +32,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index edce5fcf9e..82f58a7c98 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the welch algorithm. /// /// - public class WelchResampler : IResampler + public readonly struct WelchResampler : IResampler { /// public float Radius => 3; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -27,5 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs index eacd3834f1..de44d32e4b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs @@ -3,7 +3,6 @@ using System; using System.Numerics; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 14bf552b9d..f3521ebed9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -28,12 +28,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Gets the start index for the destination row. /// - public int StartIndex { get; } + public int StartIndex + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// Gets the the length of the kernel. /// - public int Length { get; } + public int Length + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// Gets the span representing the portion of the that this window covers. @@ -81,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Copy the contents of altering /// to the value . /// + [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) { return new ResizeKernel(left, this.bufferPtr, this.Length); @@ -96,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index 6d6e22a6a5..a79f60339d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -1,13 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Contains - /// internal partial class ResizeKernelMap { /// @@ -21,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public PeriodicKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, int sourceLength, int destinationLength, double ratio, @@ -31,7 +27,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int cornerInterval) : base( memoryAllocator, - sampler, sourceLength, destinationLength, (cornerInterval * 2) + period, @@ -45,15 +40,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - protected override void Initialize() + protected internal override void Initialize(in TResampler sampler) { // Build top corner data + one period of the mosaic data: int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; for (int i = 0; i < startOfFirstRepeatedMosaic; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } // Copy the mosaics: @@ -70,10 +64,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int bottomStartData = this.cornerInterval + this.period; for (int i = 0; i < this.cornerInterval; i++) { - ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); - this.kernels[bottomStartDest + i] = kernel; + this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 9abbb66e3a..5390cbbd18 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -1,26 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides values from an optimized, - /// contiguous memory region. + /// Provides resize kernel values from an optimized contiguous memory region. /// internal partial class ResizeKernelMap : IDisposable { private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly IResampler sampler; - private readonly int sourceLength; private readonly double ratio; @@ -35,12 +31,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ResizeKernel[] kernels; + private bool isDisposed; + // To avoid both GC allocations, and MemoryAllocator ceremony: private readonly double[] tempValues; private ResizeKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, int sourceLength, int destinationLength, int bufferHeight, @@ -48,7 +45,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms double scale, int radius) { - this.sampler = sampler; this.ratio = ratio; this.scale = scale; this.radius = radius; @@ -56,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); - this.pinHandle = this.data.Memory.Pin(); + this.pinHandle = this.data.GetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; } @@ -81,30 +77,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Disposes instance releasing it's backing buffer. /// public void Dispose() + => this.Dispose(true); + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected virtual void Dispose(bool disposing) { - this.pinHandle.Dispose(); - this.data.Dispose(); + if (!this.isDisposed) + { + this.isDisposed = true; + + if (disposing) + { + this.pinHandle.Dispose(); + this.data.Dispose(); + } + } } /// /// Returns a for an index value between 0 and DestinationSize - 1. /// [MethodImpl(InliningOptions.ShortMethod)] - public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; /// /// Computes the weights to apply at each pixel when resizing. /// + /// The type of sampler. /// The /// The destination size /// The source size /// The to use for buffer allocations /// The - public static ResizeKernelMap Calculate( - IResampler sampler, + public static ResizeKernelMap Calculate( + in TResampler sampler, int destinationSize, int sourceSize, MemoryAllocator memoryAllocator) + where TResampler : struct, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -145,7 +158,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ResizeKernelMap result = hasAtLeast2Periods ? new PeriodicKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, ratio, @@ -155,7 +167,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms cornerInterval) : new ResizeKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, destinationSize, @@ -163,17 +174,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms scale, radius); - result.Initialize(); + result.Initialize(in sampler); return result; } - protected virtual void Initialize() + /// + /// Initializes the kernel map. + /// + protected internal virtual void Initialize(in TResampler sampler) + where TResampler : struct, IResampler { for (int i = 0; i < this.DestinationLength; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } } @@ -182,7 +196,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// referencing the data at row within , /// so the data reusable by other data rows. /// - private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) + private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) + where TResampler : struct, IResampler { double center = ((destRowIndex + .5) * this.ratio) - .5; @@ -206,7 +221,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int j = left; j <= right; j++) { - double value = this.sampler.GetValue((float)((j - center) / this.scale)); + double value = sampler.GetValue((float)((j - center) / this.scale)); sum += value; kernelValues[j - left] = value; @@ -235,12 +250,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) { int length = right - left + 1; - - if (length > this.data.Width) - { - throw new InvalidOperationException( - $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); - } + this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); Span rowSpan = this.data.GetRowSpan(dataRowIndex); @@ -248,5 +258,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); return new ResizeKernel(left, rowPtr, length); } + + [Conditional("DEBUG")] + private void ValidateSizesForCreateKernel(int length, int dataRowIndex, int left, int right) + { + if (length > this.data.Width) + { + throw new InvalidOperationException( + $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index ccaa1ef9e5..4e6e7a48c1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -19,13 +17,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Guard.NotNull(options, nameof(options)); Guard.NotNull(options.Sampler, nameof(options.Sampler)); + Guard.MustBeValueType(options.Sampler, nameof(options.Sampler)); (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); this.Sampler = options.Sampler; - this.TargetWidth = size.Width; - this.TargetHeight = size.Height; - this.TargetRectangle = rectangle; + this.DestinationWidth = size.Width; + this.DestinationHeight = size.Height; + this.DestinationRectangle = rectangle; this.Compand = options.Compand; } @@ -35,19 +34,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public IResampler Sampler { get; } /// - /// Gets the target width. + /// Gets the destination width. /// - public int TargetWidth { get; } + public int DestinationWidth { get; } /// - /// Gets the target height. + /// Gets the destination height. /// - public int TargetHeight { get; } + public int DestinationHeight { get; } /// /// Gets the resize rectangle. /// - public Rectangle TargetRectangle { get; } + public Rectangle DestinationRectangle { get; } /// /// Gets a value indicating whether to compress or expand individual pixel color values on processing. @@ -55,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public bool Compand { get; } /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - => new ResizeProcessor(this, source, sourceRectangle); + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new ResizeProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 78e471ad62..6eafbda891 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -2,72 +2,45 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// Implements resizing of images using various resamplers. /// - /// - /// The original code has been adapted from . - /// /// The pixel format. - internal class ResizeProcessor : TransformProcessor - where TPixel : struct, IPixel + internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel { - private bool isDisposed; - private readonly int targetWidth; - private readonly int targetHeight; + private readonly int destinationWidth; + private readonly int destinationHeight; private readonly IResampler resampler; - private Rectangle targetRectangle; + private readonly Rectangle destinationRectangle; private readonly bool compand; + private Image destination; - // The following fields are not immutable but are optionally created on demand. - private ResizeKernelMap horizontalKernelMap; - private ResizeKernelMap verticalKernelMap; - - public ResizeProcessor(ResizeProcessor definition, Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - this.targetWidth = definition.TargetWidth; - this.targetHeight = definition.TargetHeight; - this.targetRectangle = definition.TargetRectangle; + this.destinationWidth = definition.DestinationWidth; + this.destinationHeight = definition.DestinationHeight; + this.destinationRectangle = definition.DestinationRectangle; this.resampler = definition.Sampler; this.compand = definition.Compand; } /// - protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight); + protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight); /// protected override void BeforeImageApply(Image destination) { - if (!(this.resampler is NearestNeighborResampler)) - { - Image source = this.Source; - Rectangle sourceRectangle = this.SourceRectangle; - - // Since all image frame dimensions have to be the same we can calculate this for all frames. - MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); - this.horizontalKernelMap = ResizeKernelMap.Calculate( - this.resampler, - this.targetRectangle.Width, - sourceRectangle.Width, - memoryAllocator); - - this.verticalKernelMap = ResizeKernelMap.Calculate( - this.resampler, - this.targetRectangle.Height, - sourceRectangle.Height, - memoryAllocator); - } + this.destination = destination; + this.resampler.ApplyTransform(this); base.BeforeImageApply(destination); } @@ -75,98 +48,197 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) { - Rectangle sourceRectangle = this.SourceRectangle; + // Everything happens in BeforeImageApply. + } + + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + { Configuration configuration = this.Configuration; + Image source = this.Source; + Image destination = this.destination; + Rectangle sourceRectangle = this.SourceRectangle; + Rectangle destinationRectangle = this.destinationRectangle; + bool compand = this.compand; // Handle resize dimensions identical to the original - if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.targetRectangle) + if (source.Width == destination.Width + && source.Height == destination.Height + && sourceRectangle == destinationRectangle) { - // The cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + // The cloned will be blank here copy all the pixel data over + sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); + } + return; } - int width = this.targetWidth; - int height = this.targetHeight; - int sourceX = sourceRectangle.X; - int sourceY = sourceRectangle.Y; - int startY = this.targetRectangle.Y; - int startX = this.targetRectangle.X; + var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); + + if (sampler is NearestNeighborResampler) + { + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + ApplyNNResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + sourceRectangle, + destinationRectangle, + interest); + } - var targetWorkingRect = Rectangle.Intersect( - this.targetRectangle, - new Rectangle(0, 0, width, height)); + return; + } - if (this.resampler is NearestNeighborResampler) + // Since all image frame dimensions have to be the same we can calculate + // the kernel maps and reuse for all frames. + MemoryAllocator allocator = configuration.MemoryAllocator; + using var horizontalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Width, + sourceRectangle.Width, + allocator); + + using var verticalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Height, + sourceRectangle.Height, + allocator); + + for (int i = 0; i < source.Frames.Count; i++) { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; - ParallelHelper.IterateRows( - targetWorkingRect, + ApplyResizeFrameTransform( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - // Y coordinates of source points - Span sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); - Span targetRow = destination.GetPixelRowSpan(y); - - for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; - } - } - }); - - return; + sourceFrame, + destinationFrame, + horizontalKernelMap, + verticalKernelMap, + sourceRectangle, + destinationRectangle, + interest, + compand); } + } + private static void ApplyNNResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; + + var operation = new NNRowOperation( + sourceRectangle, + destinationRectangle, + widthFactor, + heightFactor, + source, + destination); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private static void ApplyResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest, + bool compand) + { PixelConversionModifiers conversionModifiers = - PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand); + PixelConversionModifiers.Premultiply.ApplyCompanding(compand); BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); - // To reintroduce parallel processing, we to launch multiple workers + // To reintroduce parallel processing, we would launch multiple workers // for different row intervals of the image. using (var worker = new ResizeWorker( configuration, sourceArea, conversionModifiers, - this.horizontalKernelMap, - this.verticalKernelMap, - width, - targetWorkingRect, - this.targetRectangle.Location)) + horizontalKernelMap, + verticalKernelMap, + destination.Width, + interest, + destinationRectangle.Location)) { worker.Initialize(); - var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); + var workingInterval = new RowInterval(interest.Top, interest.Bottom); worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); } } - /// - protected override void Dispose(bool disposing) + private readonly struct NNRowOperation : IRowOperation { - if (this.isDisposed) + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNRowOperation( + Rectangle sourceBounds, + Rectangle destinationBounds, + float widthFactor, + float heightFactor, + ImageFrame source, + ImageFrame destination) { - return; + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + this.widthFactor = widthFactor; + this.heightFactor = heightFactor; + this.source = source; + this.destination = destination; } - if (disposing) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - this.horizontalKernelMap?.Dispose(); - this.horizontalKernelMap = null; - this.verticalKernelMap?.Dispose(); - this.verticalKernelMap = null; + int sourceX = this.sourceBounds.X; + int sourceY = this.sourceBounds.Y; + int destX = this.destinationBounds.X; + int destY = this.destinationBounds.Y; + int destLeft = this.destinationBounds.Left; + int destRight = this.destinationBounds.Right; + + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } } - - this.isDisposed = true; - base.Dispose(disposing); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 00a8cfbf3d..898809d5a5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,11 +6,8 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -21,8 +18,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// When sliding the window, the contents of the bottom window band are copied to the new top band. /// For more details, and visual explanation, see "ResizeWorker.pptx". /// - internal class ResizeWorker : IDisposable - where TPixel : struct, IPixel + internal sealed class ResizeWorker : IDisposable + where TPixel : unmanaged, IPixel { private readonly Buffer2D transposedFirstPassBuffer; @@ -76,10 +73,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.windowBandHeight = verticalKernelMap.MaxDiameter; + // We need to make sure the working buffer is contiguous: + int workingBufferLimitHintInBytes = Math.Min( + configuration.WorkingBufferSizeHintInBytes, + configuration.MemoryAllocator.GetBufferCapacityInBytes()); + int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, destWidth, - configuration.WorkingBufferSizeHintInBytes); + workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); @@ -101,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.tempColumnBuffer.Dispose(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) { return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); @@ -116,12 +118,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Span tempColSpan = this.tempColumnBuffer.GetSpan(); + // When creating transposedFirstPassBuffer, we made sure it's contiguous: + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + for (int y = rowInterval.Min; y < rowInterval.Max; y++) { // Ensure offsets are normalized for cropping and padding. ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y); - if (kernel.StartIndex + kernel.Length > this.currentWindow.Max) + while (kernel.StartIndex + kernel.Length > this.currentWindow.Max) { this.Slide(); } @@ -129,8 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); int top = kernel.StartIndex - this.currentWindow.Min; - - ref Vector4 fpBase = ref this.transposedFirstPassBuffer.Span[top]; + ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; for (int x = 0; x < this.destWidth; x++) { @@ -167,6 +171,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void CalculateFirstPassValues(RowInterval calculationInterval) { Span tempRowSpan = this.tempRowBuffer.GetSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.GetRowSpan(y); @@ -177,17 +183,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms tempRowSpan, this.conversionModifiers); - // Span firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - this.currentWindow.Min); - ref Vector4 firstPassBaseRef = ref this.transposedFirstPassBuffer.Span[y - this.currentWindow.Min]; + // optimization for: + // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); + ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++) { ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X); + // optimization for: // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs deleted file mode 100644 index 7d6ec0e08e..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Defines a rotation applicable to an . - /// - public sealed class RotateProcessor : AffineTransformProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees. - /// The source image size - public RotateProcessor(float degrees, Size sourceSize) - : this(degrees, KnownResamplers.Bicubic, sourceSize) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees. - /// The sampler to perform the rotating operation. - /// The source image size - public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) - : this( - TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), - sampler, - sourceSize) - => this.Degrees = degrees; - - // Helper constructor - private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) - : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix)) - { - } - - /// - /// Gets the angle of rotation in degrees. - /// - public float Degrees { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Image source, Rectangle sourceRectangle) - => new RotateProcessor(this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs deleted file mode 100644 index 1ed4c362c5..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Provides methods that allow the rotating of images. - /// - /// The pixel format. - internal class RotateProcessor : AffineTransformProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public RotateProcessor(RotateProcessor definition, Image source, Rectangle sourceRectangle) - : base(definition, source, sourceRectangle) - { - this.Degrees = definition.Degrees; - } - - private float Degrees { get; } - - /// - protected override void AfterImageApply(Image destination) - { - ExifProfile profile = destination.Metadata.ExifProfile; - if (profile is null) - { - return; - } - - if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) - { - // No need to do anything so return. - return; - } - - profile.RemoveValue(ExifTag.Orientation); - - base.AfterImageApply(destination); - } - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - if (this.OptimizedApply(source, destination, this.Configuration)) - { - return; - } - - base.OnFrameApply(source, destination); - } - - /// - /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range - /// - /// The angle of rotation in degrees. - /// The . - private static float WrapDegrees(float degrees) - { - degrees %= 360; - - while (degrees < 0) - { - degrees += 360; - } - - return degrees; - } - - /// - /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. - /// - /// The source image. - /// The destination image. - /// The configuration. - /// - /// The - /// - private bool OptimizedApply( - ImageFrame source, - ImageFrame destination, - Configuration configuration) - { - // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. - float degrees = WrapDegrees(this.Degrees); - - if (MathF.Abs(degrees) < Constants.Epsilon) - { - // The destination will be blank here so copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return true; - } - - if (MathF.Abs(degrees - 90) < Constants.Epsilon) - { - this.Rotate90(source, destination, configuration); - return true; - } - - if (MathF.Abs(degrees - 180) < Constants.Epsilon) - { - this.Rotate180(source, destination, configuration); - return true; - } - - if (MathF.Abs(degrees - 270) < Constants.Epsilon) - { - this.Rotate270(source, destination, configuration); - return true; - } - - return false; - } - - /// - /// Rotates the image 180 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) - { - int width = source.Width; - int height = source.Height; - - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = destination.GetPixelRowSpan(height - y - 1); - - for (int x = 0; x < width; x++) - { - targetRow[width - x - 1] = sourceRow[x]; - } - } - }); - } - - /// - /// Rotates the image 270 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) - { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - - if (destinationBounds.Contains(newX, newY)) - { - destination[newX, newY] = sourceRow[x]; - } - } - } - }); - } - - /// - /// Rotates the image 90 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) - { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - - ParallelHelper.IterateRows( - source.Bounds(), - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - int newX = height - y - 1; - for (int x = 0; x < width; x++) - { - if (destinationBounds.Contains(newX, x)) - { - destination[newX, x] = sourceRow[x]; - } - } - } - }); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs deleted file mode 100644 index fb2114e03c..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Defines a skew transformation applicable to an . - /// - public sealed class SkewProcessor : AffineTransformProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The source image size - public SkewProcessor(float degreesX, float degreesY, Size sourceSize) - : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The sampler to perform the skew operation. - /// The source image size - public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) - : this( - TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), - sampler, - sourceSize) - { - this.DegreesX = degreesX; - this.DegreesY = degreesY; - } - - // Helper constructor: - private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) - : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix)) - { - } - - /// - /// Gets the angle of rotation along the x-axis in degrees. - /// - public float DegreesX { get; } - - /// - /// Gets the angle of rotation along the y-axis in degrees. - /// - public float DegreesY { get; } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs deleted file mode 100644 index 573120888f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Contains the methods required to calculate transform kernel convolution. - /// - internal class TransformKernelMap : IDisposable - { - private readonly Buffer2D yBuffer; - private readonly Buffer2D xBuffer; - private readonly Vector2 extents; - private Vector4 maxSourceExtents; - private readonly IResampler sampler; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The source size. - /// The destination size. - /// The sampler. - public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler) - { - this.sampler = sampler; - float yRadius = this.GetSamplingRadius(source.Height, destination.Height); - float xRadius = this.GetSamplingRadius(source.Width, destination.Width); - - this.extents = new Vector2(xRadius, yRadius); - int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2); - int yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2); - - // We use 2D buffers so that we can access the weight spans per row in parallel. - this.yBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); - this.xBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); - - int maxX = source.Width - 1; - int maxY = source.Height - 1; - this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); - } - - /// - /// Gets a reference to the first item of the y window. - /// - /// The reference to the first item of the window. - [MethodImpl(InliningOptions.ShortMethod)] - public ref float GetYStartReference(int y) - => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); - - /// - /// Gets a reference to the first item of the x window. - /// - /// The reference to the first item of the window. - [MethodImpl(InliningOptions.ShortMethod)] - public ref float GetXStartReference(int y) - => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); - - public void Convolve( - Vector2 transformedPoint, - int column, - ref float ySpanRef, - ref float xSpanRef, - Buffer2D sourcePixels, - Span targetRow) - where TPixel : struct, IPixel - { - // Clamp sampling pixel radial extents to the source image edges - Vector2 minXY = transformedPoint - this.extents; - Vector2 maxXY = transformedPoint + this.extents; - - // left, top, right, bottom - var extents = new Vector4( - MathF.Ceiling(minXY.X - .5F), - MathF.Ceiling(minXY.Y - .5F), - MathF.Floor(maxXY.X + .5F), - MathF.Floor(maxXY.Y + .5F)); - - extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents); - - int left = (int)extents.X; - int top = (int)extents.Y; - int right = (int)extents.Z; - int bottom = (int)extents.W; - - if (left == right || top == bottom) - { - return; - } - - this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef); - this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef); - - Vector4 sum = Vector4.Zero; - for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) - { - float yWeight = Unsafe.Add(ref ySpanRef, kernelY); - - for (int kernelX = 0, x = left; x <= right; x++, kernelX++) - { - float xWeight = Unsafe.Add(ref xSpanRef, kernelX); - - // Values are first premultiplied to prevent darkening of edge pixels. - var current = sourcePixels[x, y].ToVector4(); - Vector4Utils.Premultiply(ref current); - sum += current * xWeight * yWeight; - } - } - - // Reverse the premultiplication - Vector4Utils.UnPremultiply(ref sum); - targetRow[column] = sum; - } - - /// - /// Calculated the normalized weights for the given point. - /// - /// The minimum sampling offset - /// The maximum sampling offset - /// The transformed point dimension - /// The reference to the collection of weights - [MethodImpl(InliningOptions.ShortMethod)] - private void CalculateWeights(int min, int max, float point, ref float weightsRef) - { - float sum = 0; - for (int x = 0, i = min; i <= max; i++, x++) - { - float weight = this.sampler.GetValue(i - point); - sum += weight; - Unsafe.Add(ref weightsRef, x) = weight; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private float GetSamplingRadius(int sourceSize, int destinationSize) - { - float scale = (float)sourceSize / destinationSize; - - if (scale < 1F) - { - scale = 1F; - } - - return MathF.Ceiling(scale * this.sampler.Radius); - } - - public void Dispose() - { - this.yBuffer?.Dispose(); - this.xBuffer?.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs index 1f0c6ebc86..5423eea88c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -11,15 +10,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal abstract class TransformProcessor : CloningImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. /// + /// The configuration which allows altering default behaviour or extending the library. /// The source for the current processor instance. /// The source area to process for the current processor instance. - protected TransformProcessor(Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + protected TransformProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs index 00c1227a6c..c1dce02be8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The pixel format. /// The image to update public static void UpdateDimensionalMetadata(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ExifProfile profile = image.Metadata.ExifProfile; if (profile is null) @@ -25,34 +25,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } - // Removing the previously stored value allows us to set a value with our own data tag if required. + // Only set the value if it already exists. if (profile.GetValue(ExifTag.PixelXDimension) != null) { - profile.RemoveValue(ExifTag.PixelXDimension); - - if (image.Width <= ushort.MaxValue) - { - profile.SetValue(ExifTag.PixelXDimension, (ushort)image.Width); - } - else - { - profile.SetValue(ExifTag.PixelXDimension, (uint)image.Width); - } + profile.SetValue(ExifTag.PixelXDimension, image.Width); } if (profile.GetValue(ExifTag.PixelYDimension) != null) { - profile.RemoveValue(ExifTag.PixelYDimension); - - if (image.Height <= ushort.MaxValue) - { - profile.SetValue(ExifTag.PixelYDimension, (ushort)image.Height); - } - else - { - profile.SetValue(ExifTag.PixelYDimension, (uint)image.Height); - } + profile.SetValue(ExifTag.PixelYDimension, image.Height); } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs new file mode 100644 index 0000000000..b474b43712 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -0,0 +1,418 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Contains utility methods for working with transforms. + /// + internal static class TransformUtilities + { + /// + /// Returns a value that indicates whether the specified matrix is degenerate + /// containing one or more values equivalent to or a + /// zero determinant and therefore cannot be used for linear transforms. + /// + /// The transform matrix. + public static bool IsDegenerate(Matrix3x2 matrix) + => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); + + /// + /// Returns a value that indicates whether the specified matrix is degenerate + /// containing one or more values equivalent to or a + /// zero determinant and therefore cannot be used for linear transforms. + /// + /// The transform matrix. + public static bool IsDegenerate(Matrix4x4 matrix) + => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); + + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsZero(float a) + => a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared; + + /// + /// Returns a value that indicates whether the specified matrix contains any values + /// that are not a number . + /// + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static bool IsNaN(Matrix3x2 matrix) + { + return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) + || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) + || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32); + } + + /// + /// Returns a value that indicates whether the specified matrix contains any values + /// that are not a number . + /// + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static bool IsNaN(Matrix4x4 matrix) + { + return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14) + || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24) + || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34) + || float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44); + } + + /// + /// Applies the projective transform against the given coordinates flattened into the 2D space. + /// + /// The "x" vector coordinate. + /// The "y" vector coordinate. + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) + { + const float Epsilon = 0.0000001F; + var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); + return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon); + } + + /// + /// Creates a centered rotation matrix using the given rotation in degrees and the source size. + /// + /// The amount of rotation, in degrees. + /// The source image size. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); + + /// + /// Creates a centered rotation matrix using the given rotation in radians and the source size. + /// + /// The amount of rotation, in radians. + /// The source image size. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); + + /// + /// Creates a centered skew matrix from the give angles in degrees and the source size. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The source image size. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); + + /// + /// Creates a centered skew matrix from the give angles in radians and the source size. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The source image size. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); + + /// + /// Gets the centered transform matrix based upon the source and destination rectangles. + /// + /// The source image bounds. + /// The transformation matrix. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) + { + Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); + + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + + var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); + + // Translate back to world space. + Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + + return centered; + } + + /// + /// Creates a matrix that performs a tapering projective transform. + /// + /// + /// The rectangular size of the image being transformed. + /// An enumeration that indicates the side of the rectangle that tapers. + /// An enumeration that indicates on which corners to taper the rectangle. + /// The amount to taper. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) + { + Matrix4x4 matrix = Matrix4x4.Identity; + + /* + * SkMatrix is laid out in the following manner: + * + * [ ScaleX SkewY Persp0 ] + * [ SkewX ScaleY Persp1 ] + * [ TransX TransY Persp2 ] + * + * When converting from Matrix4x4 to SkMatrix, the third row and + * column is dropped. When converting from SkMatrix to Matrix4x4 + * the third row and column remain as identity: + * + * [ a b c ] [ a b 0 c ] + * [ d e f ] -> [ d e 0 f ] + * [ g h i ] [ 0 0 1 0 ] + * [ g h 0 i ] + */ + switch (side) + { + case TaperSide.Left: + matrix.M11 = fraction; + matrix.M22 = fraction; + matrix.M14 = (fraction - 1) / size.Width; + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M12 = size.Height * matrix.M14; + matrix.M42 = size.Height * (1 - fraction); + break; + + case TaperCorner.Both: + matrix.M12 = size.Height * .5F * matrix.M14; + matrix.M42 = size.Height * (1 - fraction) / 2; + break; + } + + break; + + case TaperSide.Top: + matrix.M11 = fraction; + matrix.M22 = fraction; + matrix.M24 = (fraction - 1) / size.Height; + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M21 = size.Width * matrix.M24; + matrix.M41 = size.Width * (1 - fraction); + break; + + case TaperCorner.Both: + matrix.M21 = size.Width * .5F * matrix.M24; + matrix.M41 = size.Width * (1 - fraction) * .5F; + break; + } + + break; + + case TaperSide.Right: + matrix.M11 = 1 / fraction; + matrix.M14 = (1 - fraction) / (size.Width * fraction); + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M12 = size.Height * matrix.M14; + break; + + case TaperCorner.Both: + matrix.M12 = size.Height * .5F * matrix.M14; + break; + } + + break; + + case TaperSide.Bottom: + matrix.M22 = 1 / fraction; + matrix.M24 = (1 - fraction) / (size.Height * fraction); + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M21 = size.Width * matrix.M24; + break; + + case TaperCorner.Both: + matrix.M21 = size.Width * .5F * matrix.M24; + break; + } + + break; + } + + return matrix; + } + + /// + /// Returns the rectangle bounds relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + Rectangle transformed = GetTransformedRectangle(rectangle, matrix); + return new Rectangle(0, 0, transformed.Width, transformed.Height); + } + + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) + { + return rectangle; + } + + var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + + return GetBoundingRectangle(tl, tr, bl, br); + } + + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// The . + /// + public static Size GetTransformedSize(Size size, Matrix3x2 matrix) + { + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + return size; + } + + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + + return ConstrainSize(rectangle); + } + + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) + { + if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) + { + return rectangle; + } + + Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); + Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); + Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); + Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); + + return GetBoundingRectangle(tl, tr, bl, br); + } + + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Size GetTransformedSize(Size size, Matrix4x4 matrix) + { + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + return size; + } + + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + + return ConstrainSize(rectangle); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Size ConstrainSize(Rectangle rectangle) + { + // We want to resize the canvas here taking into account any translations. + int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); + int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); + + // If location in either direction is translated to a negative value equal to or exceeding the + // dimensions in either direction we need to reassign the dimension. + if (height <= 0) + { + height = rectangle.Height; + } + + if (width <= 0) + { + width = rectangle.Width; + } + + return new Size(width, height); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) + { + // Find the minimum and maximum "corners" based on the given vectors + float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); + float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); + float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); + float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); + + return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs deleted file mode 100644 index 794645550e..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Contains utility methods for working with transforms. - /// - internal static class TransformUtils - { - /// - /// Applies the projective transform against the given coordinates flattened into the 2D space. - /// - /// The "x" vector coordinate. - /// The "y" vector coordinate. - /// The transform matrix. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) - { - const float Epsilon = 0.0000001F; - var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); - return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon); - } - - /// - /// Creates a centered rotation matrix using the given rotation in degrees and the source size. - /// - /// The amount of rotation, in degrees. - /// The source image size. - /// The . - public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); - - /// - /// Creates a centered rotation matrix using the given rotation in radians and the source size. - /// - /// The amount of rotation, in radians. - /// The source image size. - /// The . - public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); - - /// - /// Creates a centered skew matrix from the give angles in degrees and the source size. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The source image size. - /// The . - public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); - - /// - /// Creates a centered skew matrix from the give angles in radians and the source size. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The source image size. - /// The . - public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size) - => CreateCenteredTransformMatrix( - new Rectangle(Point.Empty, size), - Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); - - /// - /// Gets the centered transform matrix based upon the source and destination rectangles. - /// - /// The source image bounds. - /// The transformation matrix. - /// The - public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) - { - Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); - - // We invert the matrix to handle the transformation from screen to world space. - // This ensures scaling matrices are correct. - Matrix3x2.Invert(matrix, out Matrix3x2 inverted); - - var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); - var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); - - // Translate back to world space. - Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); - - return centered; - } - - /// - /// Creates a matrix that performs a tapering projective transform. - /// - /// - /// The rectangular size of the image being transformed. - /// An enumeration that indicates the side of the rectangle that tapers. - /// An enumeration that indicates on which corners to taper the rectangle. - /// The amount to taper. - /// The - public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) - { - Matrix4x4 matrix = Matrix4x4.Identity; - - /* - * SkMatrix is laid out in the following manner: - * - * [ ScaleX SkewY Persp0 ] - * [ SkewX ScaleY Persp1 ] - * [ TransX TransY Persp2 ] - * - * When converting from Matrix4x4 to SkMatrix, the third row and - * column is dropped. When converting from SkMatrix to Matrix4x4 - * the third row and column remain as identity: - * - * [ a b c ] [ a b 0 c ] - * [ d e f ] -> [ d e 0 f ] - * [ g h i ] [ 0 0 1 0 ] - * [ g h 0 i ] - */ - switch (side) - { - case TaperSide.Left: - matrix.M11 = fraction; - matrix.M22 = fraction; - matrix.M14 = (fraction - 1) / size.Width; - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M12 = size.Height * matrix.M14; - matrix.M42 = size.Height * (1 - fraction); - break; - - case TaperCorner.Both: - matrix.M12 = size.Height * .5F * matrix.M14; - matrix.M42 = size.Height * (1 - fraction) / 2; - break; - } - - break; - - case TaperSide.Top: - matrix.M11 = fraction; - matrix.M22 = fraction; - matrix.M24 = (fraction - 1) / size.Height; - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M21 = size.Width * matrix.M24; - matrix.M41 = size.Width * (1 - fraction); - break; - - case TaperCorner.Both: - matrix.M21 = size.Width * .5F * matrix.M24; - matrix.M41 = size.Width * (1 - fraction) * .5F; - break; - } - - break; - - case TaperSide.Right: - matrix.M11 = 1 / fraction; - matrix.M14 = (1 - fraction) / (size.Width * fraction); - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M12 = size.Height * matrix.M14; - break; - - case TaperCorner.Both: - matrix.M12 = size.Height * .5F * matrix.M14; - break; - } - - break; - - case TaperSide.Bottom: - matrix.M22 = 1 / fraction; - matrix.M24 = (1 - fraction) / (size.Height * fraction); - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M21 = size.Width * matrix.M24; - break; - - case TaperCorner.Both: - matrix.M21 = size.Width * .5F * matrix.M24; - break; - } - - break; - } - - return matrix; - } - - /// - /// Returns the rectangle bounds relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) - { - Rectangle transformed = GetTransformedRectangle(rectangle, matrix); - return new Rectangle(0, 0, transformed.Width, transformed.Height); - } - - /// - /// Returns the rectangle relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) - { - if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) - { - return rectangle; - } - - var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); - var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); - var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); - var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); - - return GetBoundingRectangle(tl, tr, bl, br); - } - - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The source size. - /// The transformation matrix. - /// - /// The . - /// - public static Size GetTransformedSize(Size size, Matrix3x2 matrix) - { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - - if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) - { - return size; - } - - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - - return ConstrainSize(rectangle); - } - - /// - /// Returns the rectangle relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) - { - if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) - { - return rectangle; - } - - Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); - Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); - Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); - Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); - - return GetBoundingRectangle(tl, tr, bl, br); - } - - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The source size. - /// The transformation matrix. - /// - /// The . - /// - public static Size GetTransformedSize(Size size, Matrix4x4 matrix) - { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - - if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) - { - return size; - } - - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - - return ConstrainSize(rectangle); - } - - private static Size ConstrainSize(Rectangle rectangle) - { - // We want to resize the canvas here taking into account any translations. - int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); - int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); - - // If location in either direction is translated to a negative value equal to or exceeding the - // dimensions in either direction we need to reassign the dimension. - if (height <= 0) - { - height = rectangle.Height; - } - - if (width <= 0) - { - width = rectangle.Width; - } - - return new Size(width, height); - } - - private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) - { - // Find the minimum and maximum "corners" based on the given vectors - float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); - float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); - float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); - float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); - - return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); - } - } -} diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index c29941d071..b7e65b4cc0 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Prepend(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Appends a matrix that performs a tapering projective transform. @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Append(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Prepends a centered rotation matrix using the given rotation in degrees. @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -206,7 +206,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -264,27 +264,51 @@ namespace SixLabors.ImageSharp.Processing /// Prepends a raw matrix. /// /// The matrix to prepend. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// /// The . - public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) => this.Prepend(_ => matrix); + public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) + { + CheckDegenerate(matrix); + return this.Prepend(_ => matrix); + } /// /// Appends a raw matrix. /// /// The matrix to append. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// /// The . - public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) => this.Append(_ => matrix); + public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) + { + CheckDegenerate(matrix); + return this.Append(_ => matrix); + } /// /// Returns the combined matrix for a given source size. /// /// The source image size. /// The . - public Matrix4x4 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + public Matrix4x4 BuildMatrix(Size sourceSize) + => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); /// /// Returns the combined matrix for a given source rectangle. /// /// The rectangle in the source image. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// /// The . public Matrix4x4 BuildMatrix(Rectangle sourceRectangle) { @@ -301,9 +325,19 @@ namespace SixLabors.ImageSharp.Processing matrix *= factory(size); } + CheckDegenerate(matrix); + return matrix; } + private static void CheckDegenerate(Matrix4x4 matrix) + { + if (TransformUtilities.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + } + } + private ProjectiveTransformBuilder Prepend(Func factory) { this.matrixFactories.Insert(0, factory); @@ -316,4 +350,4 @@ namespace SixLabors.ImageSharp.Processing return this; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index ef88dc35b3..b54d2eae1c 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { diff --git a/tests/CodeCoverage/CodeCoverage.cmd b/tests/CodeCoverage/CodeCoverage.cmd deleted file mode 100644 index 01e342b3d2..0000000000 --- a/tests/CodeCoverage/CodeCoverage.cmd +++ /dev/null @@ -1,21 +0,0 @@ -@echo off - - -cd tests\CodeCoverage - -nuget restore packages.config -PackagesDirectory . - -cd .. -cd .. - -dotnet restore ImageSharp.sln -rem Clean the solution to force a rebuild with /p:codecov=true -dotnet clean ImageSharp.sln -c Release -rem The -threshold options prevents this taking ages... -tests\CodeCoverage\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:"dotnet.exe" -targetargs:"test tests\ImageSharp.Tests\ImageSharp.Tests.csproj -c Release -f netcoreapp2.1 /p:codecov=true" -register:user -threshold:10 -oldStyle -safemode:off -output:.\ImageSharp.Coverage.xml -hideskipped:All -returntargetcode -filter:"+[SixLabors.ImageSharp*]*" - -if %errorlevel% neq 0 exit /b %errorlevel% - -SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH% -pip install codecov -codecov -f "ImageSharp.Coverage.xml" \ No newline at end of file diff --git a/tests/CodeCoverage/packages.config b/tests/CodeCoverage/packages.config deleted file mode 100644 index 973b7f81b4..0000000000 --- a/tests/CodeCoverage/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 97bd9b6e7c..07d3332760 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -13,18 +13,29 @@ $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props tests + false - CS0618;$(NoWarn) + $(MSBuildThisFileDirectory)..\shared-infrastructure\SixLabors.Tests.ruleset + + $(NoWarn);CS0618 - + + + + + + + + + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index f8a4936e23..df153c08bb 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -16,4 +16,27 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs index 1ab5ed3099..6be1998fba 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.Primitives.Size; using SDImage = System.Drawing.Image; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public string TestImage { get; set; } [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public Size BmpSystemDrawing() + public SDSize BmpSystemDrawing() { using (var memoryStream = new MemoryStream(this.bmpBytes)) { @@ -43,15 +42,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Description = "ImageSharp Bmp")] - public CoreSize BmpCore() + public Size BmpCore() { using (var memoryStream = new MemoryStream(this.bmpBytes)) { using (var image = Image.Load(memoryStream)) { - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs index cc946e05ad..e4723d3a06 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.Primitives.Size; +using CoreSize = SixLabors.ImageSharp.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs index be7e853000..82dd57c299 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.Primitives.Size; using SDImage = System.Drawing.Image; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public string TestImage { get; set; } [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public Size GifSystemDrawing() + public SDSize GifSystemDrawing() { using (var memoryStream = new MemoryStream(this.gifBytes)) { @@ -43,13 +42,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Description = "ImageSharp Gif")] - public CoreSize GifCore() + public Size GifCore() { using (var memoryStream = new MemoryStream(this.gifBytes)) { using (var image = Image.Load(memoryStream)) { - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs index a19d8fa91e..b69dd36d78 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.Primitives.Size; using SDImage = System.Drawing.Image; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public Size PngSystemDrawing() + public SDSize PngSystemDrawing() { using (var memoryStream = new MemoryStream(this.pngBytes)) { @@ -43,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Description = "ImageSharp Png")] - public CoreSize PngCore() + public Size PngCore() { using (var memoryStream = new MemoryStream(this.pngBytes)) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs new file mode 100644 index 0000000000..072bd53ed7 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using System.IO; +using System.Threading; +using BenchmarkDotNet.Attributes; + +using ImageMagick; +using Pfim; +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodeTga : BenchmarkBase + { + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + private readonly PfimConfig pfimConfig = new PfimConfig(allocator: new PfimAllocator()); + + private byte[] data; + + [Params(TestImages.Tga.Bit24BottomLeft)] + public string TestImage { get; set; } + + [GlobalSetup] + public void SetupData() + { + this.data = File.ReadAllBytes(this.TestImageFullPath); + } + + [Benchmark(Baseline = true, Description = "ImageMagick Tga")] + public int TgaImageMagick() + { + var settings = new MagickReadSettings { Format = MagickFormat.Tga }; + using (var image = new MagickImage(new MemoryStream(this.data), settings)) + { + return image.Width; + } + } + + [Benchmark(Description = "ImageSharp Tga")] + public int TgaCore() + { + using (var image = Image.Load(this.data, new TgaDecoder())) + { + return image.Width; + } + } + + [Benchmark(Description = "Pfim Tga")] + public int TgaPfim() + { + using (var image = Targa.Create(this.data, this.pfimConfig)) + { + return image.Width; + } + } + + private class PfimAllocator : IImageAllocator + { + private int rented; + private readonly ArrayPool shared = ArrayPool.Shared; + + public byte[] Rent(int size) + { + return this.shared.Rent(size); + } + + public void Return(byte[] data) + { + Interlocked.Decrement(ref this.rented); + this.shared.Return(data); + } + + public int Rented => this.rented; + } + + /* RESULTS (07/01/2020) + | Method | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | + |------------------ |-------------- |-------------------- |-------------:|-------------:|-----------:|------:|-------:|------:|------:|----------:| + | 'ImageMagick Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 1,778.965 us | 1,711.088 us | 93.7905 us | 1.000 | 1.9531 | - | - | 13668 B | + | 'ImageSharp Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 38.659 us | 6.886 us | 0.3774 us | 0.022 | 0.3052 | - | - | 1316 B | + | 'Pfim Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 6.752 us | 10.268 us | 0.5628 us | 0.004 | 0.0687 | - | - | 313 B | + | | | | | | | | | | | | + | 'ImageMagick Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 1,407.585 us | 124.215 us | 6.8087 us | 1.000 | 1.9531 | - | - | 13307 B | + | 'ImageSharp Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 17.958 us | 9.352 us | 0.5126 us | 0.013 | 0.2747 | - | - | 1256 B | + | 'Pfim Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 5.645 us | 2.279 us | 0.1249 us | 0.004 | 0.0610 | - | - | 280 B | + */ + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs index 379f8aa8bf..58e3e01e39 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -16,13 +16,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] public void EncodeBmpImageSharp() { - this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new BmpEncoder()); return null; }); + this.ForEachImageSharpImage((img, ms) => + { + img.Save(ms, new BmpEncoder()); + return null; + }); } [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] public void EncodeBmpSystemDrawing() { - this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Bmp); return null; }); + this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Bmp); + return null; + }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index 89eb63d629..70c85ef022 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; @@ -6,6 +6,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -20,12 +21,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private SDImage bmpDrawing; private Image bmpCore; + // Try to get as close to System.Drawing's output as possible + private readonly GifEncoder encoder = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + + [Params(TestImages.Bmp.Car, TestImages.Png.Rgb48Bpp)] + public string TestImage { get; set; } + [GlobalSetup] public void ReadImages() { if (this.bmpStream == null) { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage)); this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = SDImage.FromStream(this.bmpStream); @@ -52,12 +62,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "ImageSharp Gif")] public void GifCore() { - // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; using (var memoryStream = new MemoryStream()) { - this.bmpCore.SaveAsGif(memoryStream, options); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index bf9627f4c1..5c7a9e991b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -1,10 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using System.Drawing.Imaging; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Benchmarks.Codecs @@ -23,15 +24,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs this.ForEachImageSharpImage((img, ms) => { // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; - img.Save(ms, options); return null; + var options = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + + img.Save(ms, options); + return null; }); } [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] public void EncodeGifSystemDrawing() { - this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Gif); return null; }); + this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Gif); + return null; + }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index 639d1594ee..aedf9cd777 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -95,9 +95,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs index 157dadd2c1..7bd1b80447 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; using System.IO; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -56,8 +57,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - this.bmpCore.SaveAsPng(memoryStream); + var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; + this.bmpCore.SaveAsPng(memoryStream, encoder); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs new file mode 100644 index 0000000000..f10eacb289 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using BenchmarkDotNet.Attributes; + +using ImageMagick; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeTga : BenchmarkBase + { + private MagickImage tgaMagick; + private Image tgaCore; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.Tga.Bit24BottomLeft)] + public string TestImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.tgaCore == null) + { + this.tgaCore = Image.Load(this.TestImageFullPath); + this.tgaMagick = new MagickImage(this.TestImageFullPath); + } + } + + [Benchmark(Baseline = true, Description = "Magick Tga")] + public void BmpSystemDrawing() + { + using (var memoryStream = new MemoryStream()) + { + this.tgaMagick.Write(memoryStream, MagickFormat.Tga); + } + } + + [Benchmark(Description = "ImageSharp Tga")] + public void BmpCore() + { + using (var memoryStream = new MemoryStream()) + { + this.tgaCore.SaveAsBmp(memoryStream); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs index f0d7a54d08..93f5bc8d85 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing; @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var image = new Image(400, 400)) { - image[200, 200] = Rgba32.White; + image[200, 200] = Color.White; return image[200, 200]; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs index 7c3da90db6..4a9d709ee7 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // This file contains small, cheap and "unit test" benchmarks to test MultiImageBenchmarkBase. @@ -6,7 +6,6 @@ // Uncomment this to enable benchmark testing // #define TEST - #if TEST // ReSharper disable InconsistentNaming @@ -76,4 +75,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } -#endif \ No newline at end of file +#endif diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs index bf9b1af338..55d66d488d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs @@ -1,44 +1,69 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - public class Block8x8F_CopyTo1x1 + public unsafe class Block8x8F_CopyTo1x1 { private Block8x8F block; + private readonly Block8x8F[] blockArray = new Block8x8F[1]; - private Buffer2D buffer; + private static readonly int Width = 100; - private BufferArea destArea; + private float[] buffer = new float[Width * 500]; + private readonly float[] unpinnedBuffer = new float[Width * 500]; + private GCHandle bufferHandle; + private GCHandle blockHandle; + private float* bufferPtr; + private float* blockPtr; [GlobalSetup] public void Setup() { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); } - this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); - this.destArea = this.buffer.GetArea(200, 100, 64, 64); + this.bufferHandle = GCHandle.Alloc(this.buffer, GCHandleType.Pinned); + this.bufferPtr = (float*)this.bufferHandle.AddrOfPinnedObject(); + + // Pin self so we can take address of to the block: + this.blockHandle = GCHandle.Alloc(this.blockArray, GCHandleType.Pinned); + this.blockPtr = (float*)Unsafe.AsPointer(ref this.block); + } + + [GlobalCleanup] + public void Cleanup() + { + this.bufferPtr = null; + this.blockPtr = null; + this.bufferHandle.Free(); + this.blockHandle.Free(); + this.buffer = null; } [Benchmark(Baseline = true)] public void Original() { ref byte selfBase = ref Unsafe.As(ref this.block); - ref byte destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride * sizeof(float); + ref byte destBase = ref Unsafe.AsRef(this.bufferPtr); + int destStride = Width * sizeof(float); CopyRowImpl(ref selfBase, ref destBase, destStride, 0); CopyRowImpl(ref selfBase, ref destBase, destStride, 1); @@ -58,12 +83,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); } - [Benchmark] + // [Benchmark] public void UseVector8() { ref Block8x8F s = ref this.block; - ref float origin = ref this.destArea.GetReferenceToOrigin(); - int stride = this.destArea.Stride; + ref float origin = ref Unsafe.AsRef(this.bufferPtr); + int stride = Width; ref Vector d0 = ref Unsafe.As>(ref origin); ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); @@ -93,12 +118,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations d7 = row7; } - [Benchmark] + // [Benchmark] public void UseVector8_V2() { ref Block8x8F s = ref this.block; - ref float origin = ref this.destArea.GetReferenceToOrigin(); - int stride = this.destArea.Stride; + ref float origin = ref Unsafe.AsRef(this.bufferPtr); + int stride = Width; ref Vector d0 = ref Unsafe.As>(ref origin); ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); @@ -119,15 +144,247 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations d7 = Unsafe.As>(ref s.V7L); } - // RESULTS: + [Benchmark] + public void UseVector8_V3() + { + int stride = Width * sizeof(float); + ref float d = ref this.unpinnedBuffer[0]; + ref Vector s = ref Unsafe.As>(ref this.block); + + Vector v0 = s; + Vector v1 = Unsafe.AddByteOffset(ref s, (IntPtr)1); + Vector v2 = Unsafe.AddByteOffset(ref s, (IntPtr)2); + Vector v3 = Unsafe.AddByteOffset(ref s, (IntPtr)3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)stride)) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 2))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 3))) = v3; + + v0 = Unsafe.AddByteOffset(ref s, (IntPtr)4); + v1 = Unsafe.AddByteOffset(ref s, (IntPtr)5); + v2 = Unsafe.AddByteOffset(ref s, (IntPtr)6); + v3 = Unsafe.AddByteOffset(ref s, (IntPtr)7); + + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 4))) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 5))) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 6))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 7))) = v3; + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void UseVector256_Avx2_Variant1() + { + int stride = Width; + float* d = this.bufferPtr; + float* s = this.blockPtr; + Vector256 v; + + v = Avx.LoadVector256(s); + Avx.Store(d, v); + + v = Avx.LoadVector256(s + 8); + Avx.Store(d + stride, v); + + v = Avx.LoadVector256(s + (8 * 2)); + Avx.Store(d + (stride * 2), v); + + v = Avx.LoadVector256(s + (8 * 3)); + Avx.Store(d + (stride * 3), v); + + v = Avx.LoadVector256(s + (8 * 4)); + Avx.Store(d + (stride * 4), v); + + v = Avx.LoadVector256(s + (8 * 5)); + Avx.Store(d + (stride * 5), v); + + v = Avx.LoadVector256(s + (8 * 6)); + Avx.Store(d + (stride * 6), v); + + v = Avx.LoadVector256(s + (8 * 7)); + Avx.Store(d + (stride * 7), v); + } + + [Benchmark] + public void UseVector256_Avx2_Variant2() + { + int stride = Width; + float* d = this.bufferPtr; + float* s = this.blockPtr; + + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 8); + Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); + Vector256 v4 = Avx.LoadVector256(s + (8 * 4)); + Vector256 v5 = Avx.LoadVector256(s + (8 * 5)); + Vector256 v6 = Avx.LoadVector256(s + (8 * 6)); + Vector256 v7 = Avx.LoadVector256(s + (8 * 7)); + + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + Avx.Store(d + (stride * 4), v4); + Avx.Store(d + (stride * 5), v5); + Avx.Store(d + (stride * 6), v6); + Avx.Store(d + (stride * 7), v7); + } + + [Benchmark] + public void UseVector256_Avx2_Variant3() + { + int stride = Width; + float* d = this.bufferPtr; + float* s = this.blockPtr; + + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 8); + Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + v0 = Avx.LoadVector256(s + (8 * 4)); + v1 = Avx.LoadVector256(s + (8 * 5)); + v2 = Avx.LoadVector256(s + (8 * 6)); + v3 = Avx.LoadVector256(s + (8 * 7)); + Avx.Store(d + (stride * 4), v0); + Avx.Store(d + (stride * 5), v1); + Avx.Store(d + (stride * 6), v2); + Avx.Store(d + (stride * 7), v3); + } + + [Benchmark] + public void UseVector256_Avx2_Variant3_RefCast() + { + int stride = Width; + ref float d = ref this.unpinnedBuffer[0]; + ref Vector256 s = ref Unsafe.As>(ref this.block); + + Vector256 v0 = s; + Vector256 v1 = Unsafe.Add(ref s, 1); + Vector256 v2 = Unsafe.Add(ref s, 2); + Vector256 v3 = Unsafe.Add(ref s, 3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.Add(ref d, stride)) = v1; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 2)) = v2; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 3)) = v3; + + v0 = Unsafe.Add(ref s, 4); + v1 = Unsafe.Add(ref s, 5); + v2 = Unsafe.Add(ref s, 6); + v3 = Unsafe.Add(ref s, 7); + + Unsafe.As>(ref Unsafe.Add(ref d, stride * 4)) = v0; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 5)) = v1; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 6)) = v2; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 7)) = v3; + } + + [Benchmark] + public void UseVector256_Avx2_Variant3_RefCast_Mod() + { + int stride = Width * sizeof(float); + ref float d = ref this.unpinnedBuffer[0]; + ref Vector256 s = ref Unsafe.As>(ref this.block); + + Vector256 v0 = s; + Vector256 v1 = Unsafe.AddByteOffset(ref s, (IntPtr)1); + Vector256 v2 = Unsafe.AddByteOffset(ref s, (IntPtr)2); + Vector256 v3 = Unsafe.AddByteOffset(ref s, (IntPtr)3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)stride)) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 2))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 3))) = v3; + + v0 = Unsafe.AddByteOffset(ref s, (IntPtr)4); + v1 = Unsafe.AddByteOffset(ref s, (IntPtr)5); + v2 = Unsafe.AddByteOffset(ref s, (IntPtr)6); + v3 = Unsafe.AddByteOffset(ref s, (IntPtr)7); + + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 4))) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 5))) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 6))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 7))) = v3; + } + + // [Benchmark] + public void UseVector256_Avx2_Variant3_WithLocalPinning() + { + int stride = Width; + fixed (float* d = this.unpinnedBuffer) + fixed (Block8x8F* ss = &this.block) + { + var s = (float*)ss; + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 8); + Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + v0 = Avx.LoadVector256(s + (8 * 4)); + v1 = Avx.LoadVector256(s + (8 * 5)); + v2 = Avx.LoadVector256(s + (8 * 6)); + v3 = Avx.LoadVector256(s + (8 * 7)); + Avx.Store(d + (stride * 4), v0); + Avx.Store(d + (stride * 5), v1); + Avx.Store(d + (stride * 6), v2); + Avx.Store(d + (stride * 7), v3); + } + } + + // [Benchmark] + public void UseVector256_Avx2_Variant3_sbyte() + { + int stride = Width * 4; + var d = (sbyte*)this.bufferPtr; + var s = (sbyte*)this.blockPtr; + + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 32); + Vector256 v2 = Avx.LoadVector256(s + (32 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (32 * 3)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + v0 = Avx.LoadVector256(s + (32 * 4)); + v1 = Avx.LoadVector256(s + (32 * 5)); + v2 = Avx.LoadVector256(s + (32 * 6)); + v3 = Avx.LoadVector256(s + (32 * 7)); + Avx.Store(d + (stride * 4), v0); + Avx.Store(d + (stride * 5), v1); + Avx.Store(d + (stride * 6), v2); + Avx.Store(d + (stride * 7), v3); + } +#endif + + // *** RESULTS 02/2020 *** + // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 + // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores + // .NET Core SDK=3.1.200-preview-014971 + // [Host] : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + // DefaultJob : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT // - // Method | Mean | Error | StdDev | Scaled | - // -------------- |---------:|----------:|----------:|-------:| - // Original | 22.53 ns | 0.1660 ns | 0.1553 ns | 1.00 | - // UseVector8 | 21.59 ns | 0.3079 ns | 0.2571 ns | 0.96 | - // UseVector8_V2 | 22.57 ns | 0.1699 ns | 0.1506 ns | 1.00 | // - // Conclusion: - // Doesn't worth to bother with this + // | Method | Mean | Error | StdDev | Ratio | RatioSD | + // |--------------------------------------- |---------:|----------:|----------:|------:|--------:| + // | Original | 4.012 ns | 0.0567 ns | 0.0531 ns | 1.00 | 0.00 | + // | UseVector8_V3 | 4.013 ns | 0.0947 ns | 0.0840 ns | 1.00 | 0.03 | + // | UseVector256_Avx2_Variant1 | 2.546 ns | 0.0376 ns | 0.0314 ns | 0.63 | 0.01 | + // | UseVector256_Avx2_Variant2 | 2.643 ns | 0.0162 ns | 0.0151 ns | 0.66 | 0.01 | + // | UseVector256_Avx2_Variant3 | 2.520 ns | 0.0760 ns | 0.0813 ns | 0.63 | 0.02 | + // | UseVector256_Avx2_Variant3_RefCast | 2.300 ns | 0.0877 ns | 0.0938 ns | 0.58 | 0.03 | + // | UseVector256_Avx2_Variant3_RefCast_Mod | 2.139 ns | 0.0698 ns | 0.0686 ns | 0.53 | 0.02 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs index 3d9b54dffa..76068ab432 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -8,8 +8,8 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { public class Block8x8F_CopyTo2x2 @@ -335,7 +335,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations Unsafe.Add(ref dBottomLeft, 7) = wRight; } - [Benchmark] public void UseVector4_V2() { @@ -409,4 +408,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations // UseVector4_SafeRightCorner | 58.97 ns | 0.4152 ns | 0.3884 ns | 0.64 | 0.02 | // UseVector4_V2 | 41.88 ns | 0.3531 ns | 0.3303 ns | 0.45 | 0.01 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs index 15a3c7eb7d..05b7156ffc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs @@ -9,7 +9,6 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { /// @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { for (int i = 0; i < Block8x8F.Size; i++) { - this.inputDividend[i] = i*44.8f; + this.inputDividend[i] = i * 44.8f; this.inputDivisor[i] = 100 - i; } } @@ -54,10 +53,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations sum = 0; for (int i = 0; i < Block8x8F.Size; i++) { - int a = (int) pDividend[i]; - int b = (int) pDivisor; + int a = (int)pDividend[i]; + int b = (int)pDivisor; result[i] = RationalRound(a, b); } + for (int i = 0; i < Block8x8F.Size; i++) { sum += result[i]; @@ -83,13 +83,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations for (int i = 0; i < Block8x8F.Size; i++) { double value = pDividend[i] / pDivisor[i]; - pDividend[i] = (float) System.Math.Round(value); + pDividend[i] = (float)System.Math.Round(value); } + for (int i = 0; i < Block8x8F.Size; i++) { - sum += (int) pDividend[i]; + sum += (int)pDividend[i]; } } + return sum; } @@ -111,6 +113,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations sum += (int)pDividend[i]; } } + return sum; } @@ -138,10 +141,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { - Vector4 sign = Vector4.Min(dividend, Vector4.One); + var sign = Vector4.Min(dividend, Vector4.One); sign = Vector4.Max(sign, MinusOne); - return dividend / divisor + sign * Half; + return (dividend / divisor) + (sign * Half); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs index 29ee402a00..5dac391165 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Numerics; @@ -10,6 +8,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { public class Block8x8F_LoadFromInt16 @@ -50,4 +49,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations // Scalar | 34.88 ns | 0.3296 ns | 0.3083 ns | 1.00 | // ExtendedAvx2 | 21.58 ns | 0.2125 ns | 0.1884 ns | 0.62 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs index 09e25827c7..32d838f8c4 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs @@ -1,22 +1,31 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - public class Block8x8F_Round + public unsafe class Block8x8F_Round { private Block8x8F block; + private readonly byte[] blockBuffer = new byte[512]; + private GCHandle blockHandle; + private float* alignedPtr; + [GlobalSetup] public void Setup() { @@ -25,13 +34,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations throw new NotSupportedException("Vector.Count != 8"); } - for (int i = 0; i < Block8x8F.Size; i++) + this.blockHandle = GCHandle.Alloc(this.blockBuffer, GCHandleType.Pinned); + ulong ptr = (ulong)this.blockHandle.AddrOfPinnedObject(); + ptr += 16; + ptr -= ptr % 16; + + if (ptr % 16 != 0) { - this.block[i] = i * 44.8f; + throw new Exception("ptr is unaligned"); } + + this.alignedPtr = (float*)ptr; } - [Benchmark(Baseline = true)] + [GlobalCleanup] + public void Cleanup() + { + this.blockHandle.Free(); + this.alignedPtr = null; + } + + [Benchmark] public void ScalarRound() { ref float b = ref Unsafe.As(ref this.block); @@ -43,8 +66,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations } } - [Benchmark] - public void SimdRound() + [Benchmark(Baseline = true)] + public void SimdUtils_FastRound_Vector8() { ref Block8x8F b = ref this.block; @@ -65,5 +88,411 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations ref Vector row7 = ref Unsafe.As>(ref b.V7L); row7 = SimdUtils.FastRound(row7); } + + [Benchmark] + public void SimdUtils_FastRound_Vector8_ForceAligned() + { + ref Block8x8F b = ref Unsafe.AsRef(this.alignedPtr); + + ref Vector row0 = ref Unsafe.As>(ref b.V0L); + row0 = SimdUtils.FastRound(row0); + ref Vector row1 = ref Unsafe.As>(ref b.V1L); + row1 = SimdUtils.FastRound(row1); + ref Vector row2 = ref Unsafe.As>(ref b.V2L); + row2 = SimdUtils.FastRound(row2); + ref Vector row3 = ref Unsafe.As>(ref b.V3L); + row3 = SimdUtils.FastRound(row3); + ref Vector row4 = ref Unsafe.As>(ref b.V4L); + row4 = SimdUtils.FastRound(row4); + ref Vector row5 = ref Unsafe.As>(ref b.V5L); + row5 = SimdUtils.FastRound(row5); + ref Vector row6 = ref Unsafe.As>(ref b.V6L); + row6 = SimdUtils.FastRound(row6); + ref Vector row7 = ref Unsafe.As>(ref b.V7L); + row7 = SimdUtils.FastRound(row7); + } + + [Benchmark] + public void SimdUtils_FastRound_Vector8_Grouped() + { + ref Block8x8F b = ref this.block; + + ref Vector row0 = ref Unsafe.As>(ref b.V0L); + ref Vector row1 = ref Unsafe.As>(ref b.V1L); + ref Vector row2 = ref Unsafe.As>(ref b.V2L); + ref Vector row3 = ref Unsafe.As>(ref b.V3L); + + row0 = SimdUtils.FastRound(row0); + row1 = SimdUtils.FastRound(row1); + row2 = SimdUtils.FastRound(row2); + row3 = SimdUtils.FastRound(row3); + + row0 = ref Unsafe.As>(ref b.V4L); + row1 = ref Unsafe.As>(ref b.V5L); + row2 = ref Unsafe.As>(ref b.V6L); + row3 = ref Unsafe.As>(ref b.V7L); + + row0 = SimdUtils.FastRound(row0); + row1 = SimdUtils.FastRound(row1); + row2 = SimdUtils.FastRound(row2); + row3 = SimdUtils.FastRound(row3); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void Sse41_V1() + { + ref Vector128 b0 = ref Unsafe.As>(ref this.block); + + ref Vector128 p = ref b0; + p = Sse41.RoundToNearestInteger(p); + + p = ref Unsafe.Add(ref b0, 1); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 2); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 3); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 4); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 5); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 6); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 7); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 8); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 9); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 10); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 11); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 12); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 13); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 14); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 15); + p = Sse41.RoundToNearestInteger(p); + } + + [Benchmark] + public unsafe void Sse41_V2() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + p = Sse41.RoundToNearestInteger(p); + var offset = (IntPtr)sizeof(Vector128); + p = Sse41.RoundToNearestInteger(p); + + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + } + + [Benchmark] + public unsafe void Sse41_V3() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + p = Sse41.RoundToNearestInteger(p); + var offset = (IntPtr)sizeof(Vector128); + + for (int i = 0; i < 15; i++) + { + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + } + } + + [Benchmark] + public unsafe void Sse41_V4() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + var offset = (IntPtr)sizeof(Vector128); + + ref Vector128 a = ref p; + ref Vector128 b = ref Unsafe.AddByteOffset(ref a, offset); + ref Vector128 c = ref Unsafe.AddByteOffset(ref b, offset); + ref Vector128 d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + + a = ref Unsafe.AddByteOffset(ref d, offset); + b = ref Unsafe.AddByteOffset(ref a, offset); + c = ref Unsafe.AddByteOffset(ref b, offset); + d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + + a = ref Unsafe.AddByteOffset(ref d, offset); + b = ref Unsafe.AddByteOffset(ref a, offset); + c = ref Unsafe.AddByteOffset(ref b, offset); + d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + + a = ref Unsafe.AddByteOffset(ref d, offset); + b = ref Unsafe.AddByteOffset(ref a, offset); + c = ref Unsafe.AddByteOffset(ref b, offset); + d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + } + + [Benchmark] + public unsafe void Sse41_V5_Unaligned() + { + float* p = this.alignedPtr + 1; + + Vector128 v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + } + + [Benchmark] + public unsafe void Sse41_V5_Aligned() + { + float* p = this.alignedPtr; + + Vector128 v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + } + + [Benchmark] + public void Sse41_V6_Aligned() + { + float* p = this.alignedPtr; + + Round8SseVectors(p); + Round8SseVectors(p + 32); + } + + private static void Round8SseVectors(float* p0) + { + float* p1 = p0 + 4; + float* p2 = p1 + 4; + float* p3 = p2 + 4; + float* p4 = p3 + 4; + float* p5 = p4 + 4; + float* p6 = p5 + 4; + float* p7 = p6 + 4; + + Vector128 v0 = Sse.LoadAlignedVector128(p0); + Vector128 v1 = Sse.LoadAlignedVector128(p1); + Vector128 v2 = Sse.LoadAlignedVector128(p2); + Vector128 v3 = Sse.LoadAlignedVector128(p3); + Vector128 v4 = Sse.LoadAlignedVector128(p4); + Vector128 v5 = Sse.LoadAlignedVector128(p5); + Vector128 v6 = Sse.LoadAlignedVector128(p6); + Vector128 v7 = Sse.LoadAlignedVector128(p7); + + v0 = Sse41.RoundToNearestInteger(v0); + v1 = Sse41.RoundToNearestInteger(v1); + v2 = Sse41.RoundToNearestInteger(v2); + v3 = Sse41.RoundToNearestInteger(v3); + v4 = Sse41.RoundToNearestInteger(v4); + v5 = Sse41.RoundToNearestInteger(v5); + v6 = Sse41.RoundToNearestInteger(v6); + v7 = Sse41.RoundToNearestInteger(v7); + + Sse.StoreAligned(p0, v0); + Sse.StoreAligned(p1, v1); + Sse.StoreAligned(p2, v2); + Sse.StoreAligned(p3, v3); + Sse.StoreAligned(p4, v4); + Sse.StoreAligned(p5, v5); + Sse.StoreAligned(p6, v6); + Sse.StoreAligned(p7, v7); + } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index f40c15cc14..51da291726 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,11 +1,13 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using BenchmarkDotNet.Attributes; using System.Drawing; using System.IO; +using BenchmarkDotNet.Attributes; + using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { @@ -26,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark(Baseline = true, Description = "System.Drawing FULL")] - public Size JpegSystemDrawing() + public SDSize JpegSystemDrawing() { using (var memoryStream = new MemoryStream(this.jpegBytes)) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index f8a7556ca5..06492bc92d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// @@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg this.ForEachStream(SDImage.FromStream); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 99b071e59e..1696623ef1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -1,20 +1,20 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.Primitives.Size; using SDImage = System.Drawing.Image; -// ReSharper disable InconsistentNaming +using SDSize = System.Drawing.Size; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// @@ -34,10 +34,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { public ShortClr() { - this.Add( - // Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), - Job.Core.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3) - ); + // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), + this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); } } } @@ -46,6 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + #pragma warning disable SA1115 [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, @@ -53,14 +52,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // The scaled result for the large image "ExifGetString750Transform_Huge420YCbCr" // is almost the same as the result for Jpeg420Exif, // which proves that the execution time for the most common YCbCr 420 path scales linearly. - // // TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr - )] public string TestImage { get; set; } - [GlobalSetup] public void ReadImages() { @@ -71,7 +67,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark(Baseline = true, Description = "Decode Jpeg - System.Drawing")] - public Size JpegSystemDrawing() + public SDSize JpegSystemDrawing() { using (var memoryStream = new MemoryStream(this.jpegBytes)) { @@ -83,13 +79,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark(Description = "Decode Jpeg - ImageSharp")] - public CoreSize JpegImageSharp() + public Size JpegImageSharp() { using (var memoryStream = new MemoryStream(this.jpegBytes)) { using (var image = Image.Load(memoryStream, new JpegDecoder { IgnoreMetadata = true })) { - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } @@ -101,7 +97,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC // .NET Core SDK=2.1.403 // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // + // // Method | TestImage | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // ------------------------------- |-------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|----------:|---------:|---------:|------------:| // 'Decode Jpeg - System.Drawing' | Jpg/baseline/Lake.jpg | 6.117 ms | 0.3923 ms | 0.0222 ms | 1.00 | 0.00 | 62.5000 | - | - | 205.83 KB | @@ -118,21 +114,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // RESULTS (2019 April 23): // - //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) - //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores - //.NET Core SDK=2.2.202 + // BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + // Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + // .NET Core SDK=2.2.202 // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // - //| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - //|------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:| - //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB | - //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB | - //| | | | | | | | | | | | - //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | - //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB | - //| | | | | | | | | | | | - //| 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB | - //| 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB | + // | Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:| + // | 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB | + // | 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB | + // | | | | | | | | | | | | + // | 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | + // | 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB | + // | | | | | | | | | | | | + // | 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB | + // | 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB | } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs index 6f2c492d0a..6f3ea0e142 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs @@ -125,28 +125,29 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } - // RESULTS (2019 April 24): - // - //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) - //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores - //.NET Core SDK=2.2.202 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - //IterationCount=3 LaunchCount=1 WarmupCount=3 - // - //| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - //|----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| - //| StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - | - //| StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - | - //| DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - | - //| DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - | - //| SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - | - //| | | | | | | | | | | | | - //| StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - | - //| StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - | - //| DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - | - //| DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - | - //| SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - | + /* RESULTS (2019 April 24): + + BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + .NET Core SDK=2.2.202 + [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + + IterationCount=3 LaunchCount=1 WarmupCount=3 + + | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + |----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + | StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - | + | StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - | + | DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - | + | DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - | + | SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - | + | | | | | | | | | | | | | + | StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - | + | StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - | + | DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - | + | DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - | + | SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - | + */ } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index c617d25c07..b64c86974a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -1,16 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using System.Drawing; using System.Drawing.Imaging; using System.IO; - using CoreImage = SixLabors.ImageSharp.Image; public class EncodeJpeg : BenchmarkBase @@ -58,4 +57,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs index afa2ad325a..a710fc1965 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -18,13 +18,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] public void EncodeJpegImageSharp() { - this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new JpegEncoder()); return null; }); + this.ForEachImageSharpImage((img, ms) => + { + img.Save(ms, new JpegEncoder()); + return null; + }); } [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] public void EncodeJpegSystemDrawing() { - this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Jpeg); return null; }); + this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Jpeg); + return null; + }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs index e39cfa6ba2..4a3c88a281 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,8 +13,8 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(MultiImageBenchmarkBase.Config))] @@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs index 1834f77eaf..0d0e3212b1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs @@ -1,18 +1,20 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using BenchmarkDotNet.Attributes; using System; -using System.IO; -using SixLabors.ImageSharp.Tests; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; +using System.IO; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; + using SDImage = System.Drawing.Image; -using SixLabors.ImageSharp.Formats.Jpeg; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(Config.ShortClr))] @@ -29,9 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr - )] + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] public string TestImage { get; set; } [Params(false, true)] @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT // Job-ZPEZGV : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 // Job-SGOCJT : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // + // // Method | Runtime | TestImage | ParallelExec | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | // -------------- |-------- |----------------------------- |------------- |----------:|----------:|----------:|-------:|---------:|---------:|----------:| // SystemDrawing | Clr | Jpg/baseline/jpeg420exif.jpg | False | 64.88 ms | 3.735 ms | 0.2110 ms | 1.00 | 0.00 | 250.0000 | 791.07 KB | @@ -104,4 +104,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // SystemDrawing | Core | Jpg/baseline/jpeg420exif.jpg | True | 64.20 ms | 6.560 ms | 0.3707 ms | 1.00 | 0.00 | 250.0000 | 789.79 KB | // ImageSharp | Core | Jpg/baseline/jpeg420exif.jpg | True | 68.08 ms | 18.376 ms | 1.0383 ms | 1.06 | 0.01 | - | 50.49 KB | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs index 313a7c97d3..1daf9b4d5a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } - [Benchmark(Baseline = true)] + [Benchmark] public void Scalar() { var values = new JpegColorConverter.ComponentValues(this.input, 0); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F); } - [Benchmark] + [Benchmark(Baseline = true)] public void SimdVector4() { var values = new JpegColorConverter.ComponentValues(this.input, 0); @@ -53,11 +53,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public void SimdAvx2() + public void SimdVector8() { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F); + JpegColorConverter.FromYCbCrSimdVector8.ConvertCore(values, this.output, 255F, 128F); } private static Buffer2D[] CreateRandomValues( @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } // no need to dispose when buffer is not array owner diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index bf694211de..eafbc0fdeb 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -17,9 +17,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; + using BenchmarkDotNet.Environments; using SixLabors.ImageSharp.Tests; - using CoreImage = ImageSharp.Image; + using CoreImage = SixLabors.ImageSharp.Image; public abstract class MultiImageBenchmarkBase { @@ -35,27 +36,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { public ShortClr() { - this.Add( - Job.Core.WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2) - ); + this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2)); } } } - protected Dictionary FileNamesToBytes = new Dictionary(); - - protected Dictionary> FileNamesToImageSharpImages = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); + protected Dictionary fileNamesToBytes = new Dictionary(); + protected Dictionary> fileNamesToImageSharpImages = new Dictionary>(); + protected Dictionary fileNamesToSystemDrawingImages = new Dictionary(); /// - /// The values of this enum separate input files into categories + /// The values of this enum separate input files into categories. /// public enum InputImageCategory { + /// + /// Use all images. + /// AllImages, + /// + /// Use small images only. + /// SmallImagesOnly, + /// + /// Use large images only. + /// LargeImagesOnly } @@ -72,12 +79,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; /// - /// Enumerates folders containing files OR files to be processed by the benchmark. + /// Gets folders containing files OR files to be processed by the benchmark. /// protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// - /// The images sized above this threshold will be included in + /// Gets the large image threshold. + /// The images sized above this threshold will be included in. /// protected virtual int LargeImageThresholdInBytes => 100000; @@ -101,7 +109,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected IEnumerable> FileNames2Bytes => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToBytes, + this.fileNamesToBytes, arr => arr.Length < this.LargeImageThresholdInBytes); protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } @@ -113,6 +121,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); } + // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); this.ReadFilesImpl(); } @@ -123,7 +132,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { if (File.Exists(path)) { - this.FileNamesToBytes[path] = File.ReadAllBytes(path); + this.fileNamesToBytes[path] = File.ReadAllBytes(path); continue; } @@ -137,7 +146,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs foreach (string fn in allFiles) { - this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + this.fileNamesToBytes[fn] = File.ReadAllBytes(fn); } } } @@ -156,7 +165,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { object obj = operation(memoryStream); (obj as IDisposable)?.Dispose(); - } catch (Exception ex) { @@ -172,31 +180,30 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { base.ReadFilesImpl(); - foreach (KeyValuePair kv in this.FileNamesToBytes) + foreach (KeyValuePair kv in this.fileNamesToBytes) { byte[] bytes = kv.Value; string fn = kv.Key; using (var ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); - + this.fileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); } - this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); + this.fileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); } } protected IEnumerable>> FileNames2ImageSharpImages => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToImageSharpImages, + this.fileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToSystemDrawingImages, + this.fileNamesToSystemDrawingImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected virtual int LargeImageThresholdInPixels => 700000; @@ -209,13 +216,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); - } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } - } } @@ -223,13 +228,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var workStream = new MemoryStream()) { - this.ForEachImageSharpImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure return result; }); @@ -256,18 +261,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var workStream = new MemoryStream()) { - this.ForEachSystemDrawingImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure return result; }); } - } } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs index b964221764..dc1d21c14d 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs @@ -1,20 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers; using System; - +using System.Buffers; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class FromRgba32Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner destination; @@ -23,7 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk private Configuration configuration; [Params( - 128, + 128, 1024, 2048)] public int Count { get; set; } @@ -43,12 +41,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.source.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); Span d = this.destination.GetSpan(); - + for (int i = 0; i < this.Count; i++) { int i4 = i * 4; @@ -89,4 +87,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // CommonBulk | 2048 | 2,625.4 ns | 30.143 ns | 26.721 ns | 1.00 | // OptimizedBulk | 2048 | 1,843.0 ns | 20.505 ns | 18.177 ns | 0.70 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs index 8b2d08e66a..1184bef2e0 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -1,24 +1,29 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { [Config(typeof(Config.ShortClr))] public abstract class FromVector4 - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { protected IMemoryOwner source; @@ -26,10 +31,8 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk protected Configuration Configuration => Configuration.Default; - [Params( - 64, - 2048 - )] + // [Params(64, 2048)] + [Params(1024)] public int Count { get; set; } [GlobalSetup] @@ -46,12 +49,11 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.source.Dispose(); } - //[Benchmark] + // [Benchmark] public void PerElement() { ref Vector4 s = ref MemoryMarshal.GetReference(this.source.GetSpan()); ref TPixel d = ref MemoryMarshal.GetReference(this.destination.GetSpan()); - for (int i = 0; i < this.Count; i++) { Unsafe.Add(ref d, i).FromVector4(Unsafe.Add(ref s, i)); @@ -79,52 +81,105 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(sBytes, dFloats); } - [Benchmark(Baseline = true)] + [Benchmark] public void BasicIntrinsics256() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.BasicIntrinsics256.NormalizedFloatToByteSaturate(sBytes, dFloats); } - [Benchmark] + [Benchmark(Baseline = true)] public void ExtendedIntrinsic() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); } - // RESULTS (2018 October): - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |-------------:|-------------:|------------:|-------:|---------:|-------:|----------:| - // FallbackIntrinsics128 | Clr | 64 | 340.38 ns | 22.319 ns | 1.2611 ns | 1.41 | 0.01 | - | 0 B | - // BasicIntrinsics256 | Clr | 64 | 240.79 ns | 11.421 ns | 0.6453 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Clr | 64 | 199.09 ns | 124.239 ns | 7.0198 ns | 0.83 | 0.02 | - | 0 B | - // PixelOperations_Base | Clr | 64 | 647.99 ns | 24.003 ns | 1.3562 ns | 2.69 | 0.01 | 0.0067 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 259.79 ns | 13.391 ns | 0.7566 ns | 1.08 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 64 | 234.64 ns | 12.320 ns | 0.6961 ns | 1.58 | 0.00 | - | 0 B | - // BasicIntrinsics256 | Core | 64 | 148.87 ns | 2.794 ns | 0.1579 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Core | 64 | 94.06 ns | 10.015 ns | 0.5659 ns | 0.63 | 0.00 | - | 0 B | - // PixelOperations_Base | Core | 64 | 573.52 ns | 31.865 ns | 1.8004 ns | 3.85 | 0.01 | 0.0067 | 24 B | - // PixelOperations_Specialized | Core | 64 | 117.21 ns | 13.264 ns | 0.7494 ns | 0.79 | 0.00 | - | 0 B | - // | | | | | | | | | | - // FallbackIntrinsics128 | Clr | 2048 | 6,735.93 ns | 2,139.340 ns | 120.8767 ns | 1.71 | 0.03 | - | 0 B | - // BasicIntrinsics256 | Clr | 2048 | 3,929.29 ns | 334.027 ns | 18.8731 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Clr | 2048 | 2,226.01 ns | 130.525 ns | 7.3749 ns |!! 0.57 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Clr | 2048 | 16,760.84 ns | 367.800 ns | 20.7814 ns | 4.27 | 0.02 | - | 24 B | <--- Extra copies using "Vector4 TPixel.ToVector4()" - // PixelOperations_Specialized | Clr | 2048 | 3,986.03 ns | 237.238 ns | 13.4044 ns | 1.01 | 0.00 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 2048 | 6,644.65 ns | 2,677.090 ns | 151.2605 ns | 1.69 | 0.05 | - | 0 B | - // BasicIntrinsics256 | Core | 2048 | 3,923.70 ns | 1,971.760 ns | 111.4081 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Core | 2048 | 2,092.32 ns | 375.657 ns | 21.2253 ns |!! 0.53 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Core | 2048 | 16,875.73 ns | 1,271.957 ns | 71.8679 ns | 4.30 | 0.10 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 2,129.92 ns | 262.888 ns | 14.8537 ns |!! 0.54 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void UseAvx2() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.Avx2Intrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); + } + + private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 }; + + [Benchmark] + public void UseAvx2_Grouped() + { + Span src = MemoryMarshal.Cast(this.source.GetSpan()); + Span dest = MemoryMarshal.Cast(this.destination.GetSpan()); + + int n = dest.Length / Vector.Count; + + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(src)); + ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); + Vector256 mask = Unsafe.As>(ref maskBase); + + var maxBytes = Vector256.Create(255f); + + for (int i = 0; i < n; i++) + { + ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); + + Vector256 f0 = s; + Vector256 f1 = Unsafe.Add(ref s, 1); + Vector256 f2 = Unsafe.Add(ref s, 2); + Vector256 f3 = Unsafe.Add(ref s, 3); + + f0 = Avx.Multiply(maxBytes, f0); + f1 = Avx.Multiply(maxBytes, f1); + f2 = Avx.Multiply(maxBytes, f2); + f3 = Avx.Multiply(maxBytes, f3); + + Vector256 w0 = Avx.ConvertToVector256Int32(f0); + Vector256 w1 = Avx.ConvertToVector256Int32(f1); + Vector256 w2 = Avx.ConvertToVector256Int32(f2); + Vector256 w3 = Avx.ConvertToVector256Int32(f3); + + Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); + Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); + Vector256 b = Avx2.PackUnsignedSaturate(u0, u1); + b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte(); + + Unsafe.Add(ref destBase, i) = b; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(scale, vf); + return Avx.ConvertToVector256Int32(vf); + } +#endif + + // *** RESULTS 2020 March: *** + // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores + // .NET Core SDK=3.1.200-preview-014971 + // Job-IUZXZT : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + // + // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |---------------------------- |------ |-----------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | FallbackIntrinsics128 | 1024 | 2,952.6 ns | 1,680.77 ns | 92.13 ns | 3.32 | 0.16 | - | - | - | - | + // | BasicIntrinsics256 | 1024 | 1,664.5 ns | 928.11 ns | 50.87 ns | 1.87 | 0.09 | - | - | - | - | + // | ExtendedIntrinsic | 1024 | 890.6 ns | 375.48 ns | 20.58 ns | 1.00 | 0.00 | - | - | - | - | + // | UseAvx2 | 1024 | 299.0 ns | 30.47 ns | 1.67 ns | 0.34 | 0.01 | - | - | - | - | + // | UseAvx2_Grouped | 1024 | 318.1 ns | 48.19 ns | 2.64 ns | 0.36 | 0.01 | - | - | - | - | + // | PixelOperations_Base | 1024 | 8,136.9 ns | 1,834.82 ns | 100.57 ns | 9.14 | 0.26 | - | - | - | 24 B | + // | PixelOperations_Specialized | 1024 | 951.1 ns | 123.93 ns | 6.79 ns | 1.07 | 0.03 | - | - | - | - | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs index 294baa9d51..dfcc516468 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System.Buffers; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class Rgb24Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner source; @@ -57,4 +56,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk public class Rgb24Bytes_Rgba32 : Rgb24Bytes { } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs index 7f4b2bc41d..c21c0abf57 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,11 +9,10 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class ToRgba32Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner source; @@ -39,7 +38,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 70de8f4e27..b57136a929 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -1,21 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers; using System; +using System.Buffers; using System.Numerics; - using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class ToVector4 - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { protected IMemoryOwner source; @@ -23,12 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk protected Configuration Configuration => Configuration.Default; - [Params( - 64, - 256, - //512, - //1024, - 2048)] + [Params(64, 256, 2048)] // 512, 1024 public int Count { get; set; } [GlobalSetup] @@ -45,7 +38,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); @@ -56,7 +49,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk d[i] = s[i].ToVector4(); } } - [Benchmark] public void PixelOperations_Specialized() @@ -67,4 +59,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.GetSpan()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs index 39702d5253..3a69a6e246 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -38,4 +41,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B | // PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs index ab05a14073..483ab61741 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -19,7 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(sBytes, dFloats); } [Benchmark] @@ -37,7 +40,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.BasicIntrinsics256.ByteToNormalizedFloat(sBytes, dFloats); } [Benchmark] @@ -46,10 +49,10 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); } - //[Benchmark] + // [Benchmark] public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); @@ -91,7 +94,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk } } - //[Benchmark] + // [Benchmark] public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); @@ -127,38 +130,39 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) { - Vector vi = Vector.AsVectorInt32(u); - Vector v = Vector.ConvertToSingle(vi); + var vi = Vector.AsVectorInt32(u); + var v = Vector.ConvertToSingle(vi); v *= scale; return v; } - // RESULTS (2018 October): - // - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| - // FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | - // BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | - // PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | - // BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | - // PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | - // PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | - // | | | | | | | | | | - // FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | - // BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | - // BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + /*RESULTS (2018 October): + + Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| + FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | + BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | + PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | + PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! + | | | | | | | | | | + FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | + BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | + PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | + PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | + | | | | | | | | | | + FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | + BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | + PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( + | | | | | | | | | | + FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | + BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | + PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + */ } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs index 855f5b9b40..5ca5849173 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; @@ -18,7 +21,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces return ColorSpaceConverter.ToCieLab(CieXyz).L; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs index 07870b3a85..3f9d1648cb 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; @@ -30,4 +33,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces return ColorSpaceConverter.ToHunterLab(CieXyz).L; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs index 4d9ba89286..f82afaac47 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs index f20ffdcabc..59705a2023 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -1,11 +1,14 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; + namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { public class ColorspaceCieXyzToRgbConvert @@ -18,7 +21,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces return ColorSpaceConverter.ToRgb(CieXyz).R; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs index 335ecf4789..a2290ce1f7 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Benchmarks +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks { public partial class RgbToYCbCr { @@ -234,4 +237,4 @@ }; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs index 0571513f56..b11e389af7 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs @@ -98,6 +98,7 @@ namespace SixLabors.ImageSharp.Benchmarks { result.Data[i] = data[i]; } + return result; } } @@ -125,6 +126,7 @@ namespace SixLabors.ImageSharp.Benchmarks { this.inputSourceRGB[i] = (byte)(42 + i); } + this.inputSourceRGBAsInteger = new int[InputByteCount + Vector.Count]; // Filling this should be part of the measured operation } @@ -139,7 +141,6 @@ namespace SixLabors.ImageSharp.Benchmarks var yPtr = (float*)&result.Y; var cbPtr = (float*)&result.Cb; var crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { @@ -165,7 +166,6 @@ namespace SixLabors.ImageSharp.Benchmarks var yPtr = (float*)&result.Y; var cbPtr = (float*)&result.Cb; var crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { @@ -174,8 +174,7 @@ namespace SixLabors.ImageSharp.Benchmarks var vectorRgb = new Vector3( input.Data[i3 + 0], input.Data[i3 + 1], - input.Data[i3 + 2] - ); + input.Data[i3 + 2]); Vector3 vectorY = VectorY * vectorRgb; Vector3 vectorCb = VectorCb * vectorRgb; @@ -197,7 +196,6 @@ namespace SixLabors.ImageSharp.Benchmarks var yPtr = (float*)&result.Y; var cbPtr = (float*)&result.Cb; var crPtr = (float*)&result.Cr; - // end of code-bloat block :) var yCoeffs = new Vector(ScaledCoeffs.Y); var cbCoeffs = new Vector(ScaledCoeffs.Cb); @@ -243,7 +241,6 @@ namespace SixLabors.ImageSharp.Benchmarks float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) var yCoeffs = new Vector(ScaledCoeffs.Y); var cbCoeffs = new Vector(ScaledCoeffs.Cb); @@ -306,7 +303,6 @@ namespace SixLabors.ImageSharp.Benchmarks float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { @@ -345,7 +341,6 @@ namespace SixLabors.ImageSharp.Benchmarks float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index 060a28550e..b8e58a8c5a 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; diff --git a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs index 2e3307d298..5d3bc26bae 100644 --- a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs +++ b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Benchmarks +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks { using System.Numerics; diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 0543cbc50d..fda98a097c 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,8 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +#if Windows_NT +using System.Security.Principal; +using BenchmarkDotNet.Diagnostics.Windows; +#endif using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; namespace SixLabors.ImageSharp.Benchmarks @@ -12,6 +17,14 @@ namespace SixLabors.ImageSharp.Benchmarks public Config() { this.Add(MemoryDiagnoser.Default); + +#if Windows_NT + if (this.IsElevated) + { + this.Add(new NativeMemoryProfiler()); + } +#endif + } public class ShortClr : Config @@ -19,10 +32,28 @@ namespace SixLabors.ImageSharp.Benchmarks public ShortClr() { this.Add( - Job.Clr.WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Core.WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3) - ); + Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } + } + + public class ShortCore31 : Config + { + public ShortCore31() + { + this.Add(Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } + } + +#if Windows_NT + private bool IsElevated + { + get + { + return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); } } +#endif } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs deleted file mode 100644 index 8f4a7dfcb7..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using System.IO; -using System.Numerics; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public class DrawBeziers : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")] - public void DrawPathSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - - using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) - { - graphics.DrawBeziers(pen, new[] { - new PointF(10, 500), - new PointF(30, 10), - new PointF(240, 30), - new PointF(300, 500) - }); - } - - using (var stream = new MemoryStream()) - { - destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Beziers")] - public void DrawLinesCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.DrawBeziers( - Rgba32.HotPink, - 10, - new Vector2(10, 500), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 500))); - - using (var stream = new MemoryStream()) - { - image.SaveAsBmp(stream); - } - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs deleted file mode 100644 index 43b7672c47..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using System.IO; -using System.Numerics; - -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public class DrawLines : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing Draw Lines")] - public void DrawPathSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - - using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) - { - graphics.DrawLines(pen, new[] { - new PointF(10, 10), - new PointF(550, 50), - new PointF(200, 400) - }); - } - - using (var stream = new MemoryStream()) - { - destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Lines")] - public void DrawLinesCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.DrawLines( - Rgba32.HotPink, - 10, - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400))); - - using (var stream = new MemoryStream()) - { - image.SaveAsBmp(stream); - } - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs deleted file mode 100644 index f20469b63d..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; -using System.IO; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public class DrawPolygon : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing Draw Polygon")] - public void DrawPolygonSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) - { - graphics.DrawPolygon(pen, new[] { - new PointF(10, 10), - new PointF(550, 50), - new PointF(200, 400) - }); - } - - using (var stream = new MemoryStream()) - { - destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Polygon")] - public void DrawPolygonCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.DrawPolygon( - Rgba32.HotPink, - 10, - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400))); - - using (var ms = new MemoryStream()) - { - image.SaveAsBmp(ms); - } - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs deleted file mode 100644 index 0982db3340..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using System.Linq; -using SixLabors.ImageSharp.Processing.Processors.Text; - -namespace SixLabors.ImageSharp.Benchmarks -{ - [MemoryDiagnoser] - public class DrawText : BenchmarkBase - { - [Params(10, 100)] - public int TextIterations { get; set; } - public string TextPhrase { get; set; } = "Hello World"; - public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations)); - - - [Benchmark(Baseline = true, Description = "System.Drawing Draw Text")] - public void DrawTextSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - using (var font = new Font("Arial", 12, GraphicsUnit.Point)) - { - graphics.DrawString(TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780)); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Text - Cached Glyphs")] - public void DrawTextCore() - { - using (var image = new Image(800, 800)) - { - var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)))); - } - } - - [Benchmark(Description = "ImageSharp Draw Text - Nieve")] - public void DrawTextCoreOld() - { - using (var image = new Image(800, 800)) - { - var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))); - } - - IImageProcessingContext DrawTextOldVersion( - IImageProcessingContext source, - TextGraphicsOptions options, - string text, - SixLabors.Fonts.Font font, - IBrush brush, - IPen pen, - SixLabors.Primitives.PointF location) - { - float dpiX = 72; - float dpiY = 72; - - var style = new SixLabors.Fonts.RendererOptions(font, dpiX, dpiY, location) - { - ApplyKerning = options.ApplyKerning, - TabWidth = options.TabWidth, - WrappingWidth = options.WrapTextWidth, - HorizontalAlignment = options.HorizontalAlignment, - VerticalAlignment = options.VerticalAlignment - }; - - Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); - - var pathOptions = (GraphicsOptions)options; - if (brush != null) - { - source.Fill(pathOptions, brush, glyphs); - } - - if (pen != null) - { - source.Draw(pathOptions, pen, glyphs); - } - - return source; - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs deleted file mode 100644 index c5c1ba5ac1..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using System.Linq; -using SixLabors.ImageSharp.Processing.Processors.Text; - -namespace SixLabors.ImageSharp.Benchmarks -{ - [MemoryDiagnoser] - public class DrawTextOutline : BenchmarkBase - { - [Params(10, 100)] - public int TextIterations { get; set; } - public string TextPhrase { get; set; } = "Hello World"; - public string TextToRender => string.Join(" ", Enumerable.Repeat(TextPhrase, TextIterations)); - - [Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")] - public void DrawTextSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) - using (var font = new Font("Arial", 12, GraphicsUnit.Point)) - using (var gp = new GraphicsPath()) - { - gp.AddString(TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat()); - graphics.DrawPath(pen, gp); - } - } - } - - [Benchmark(Description = "ImageSharp Draw Text Outline - Cached Glyphs")] - public void DrawTextCore() - { - using (var image = new Image(800, 800)) - { - var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10)))); - } - } - - [Benchmark(Description = "ImageSharp Draw Text Outline - Nieve")] - public void DrawTextCoreOld() - { - using (var image = new Image(800, 800)) - { - var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); - image.Mutate( - x => DrawTextOldVersion( - x, - new TextGraphicsOptions(true) { WrapTextWidth = 780 }, - TextToRender, - font, - null, - Processing.Pens.Solid(Rgba32.HotPink, 10), - new SixLabors.Primitives.PointF(10, 10))); - } - - IImageProcessingContext DrawTextOldVersion( - IImageProcessingContext source, - TextGraphicsOptions options, - string text, - SixLabors.Fonts.Font font, - IBrush brush, - IPen pen, - SixLabors.Primitives.PointF location) - { - var style = new SixLabors.Fonts.RendererOptions(font, options.DpiX, options.DpiY, location) - { - ApplyKerning = options.ApplyKerning, - TabWidth = options.TabWidth, - WrappingWidth = options.WrapTextWidth, - HorizontalAlignment = options.HorizontalAlignment, - VerticalAlignment = options.VerticalAlignment - }; - - Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); - - var pathOptions = (GraphicsOptions)options; - if (brush != null) - { - source.Fill(pathOptions, brush, glyphs); - } - - if (pen != null) - { - source.Draw(pathOptions, pen, glyphs); - } - - return source; - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs deleted file mode 100644 index f33df7ec62..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using System.IO; -using System.Numerics; -using SixLabors.Shapes; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public class FillPolygon : BenchmarkBase - { - private readonly Polygon shape; - - public FillPolygon() - { - this.shape = new Polygon(new LinearLineSegment( - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400))); - } - - [Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")] - public void DrawSolidPolygonSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - - using (var graphics = Graphics.FromImage(destination)) - { - graphics.SmoothingMode = SmoothingMode.AntiAlias; - graphics.FillPolygon(System.Drawing.Brushes.HotPink, - new[] { - new Point(10, 10), - new Point(550, 50), - new Point(200, 400) - }); - - using (var stream = new MemoryStream()) - { - destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Fill Polygon")] - public void DrawSolidPolygonCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.FillPolygon( - Rgba32.HotPink, - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400))); - - using (var stream = new MemoryStream()) - { - image.SaveAsBmp(stream); - } - } - } - - [Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] - public void DrawSolidPolygonCoreCached() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.Fill( - Rgba32.HotPink, - this.shape)); - - using (var stream = new MemoryStream()) - { - image.SaveAsBmp(stream); - } - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs deleted file mode 100644 index 531c540da7..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Numerics; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using CoreRectangle = SixLabors.Primitives.Rectangle; -using CoreSize = SixLabors.Primitives.Size; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public class FillRectangle : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing Fill Rectangle")] - public Size FillRectangleSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - graphics.FillRectangle(System.Drawing.Brushes.HotPink, new Rectangle(10, 10, 190, 140)); - - return destination.Size; - } - } - - [Benchmark(Description = "ImageSharp Fill Rectangle")] - public CoreSize FillRectangleCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.Fill(Rgba32.HotPink, new CoreRectangle(10, 10, 190, 140))); - - return new CoreSize(image.Width, image.Height); - } - } - - [Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")] - public CoreSize FillPolygonCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.FillPolygon( - Rgba32.HotPink, - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150))); - - return new CoreSize(image.Width, image.Height); - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs deleted file mode 100644 index 411f8210a9..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using System.IO; - -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using CoreBrushes = SixLabors.ImageSharp.Processing.Brushes; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public class FillWithPattern - { - [Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")] - public void DrawPatternPolygonSystemDrawing() - { - using (var destination = new Bitmap(800, 800)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.SmoothingMode = SmoothingMode.AntiAlias; - - using (var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink)) - { - graphics.FillRectangle(brush, new Rectangle(0, 0, 800, 800)); // can't find a way to flood fill with a brush - } - - using (var stream = new MemoryStream()) - { - destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); - } - } - } - - [Benchmark(Description = "ImageSharp Fill with Pattern")] - public void DrawPatternPolygon3Core() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Rgba32.HotPink))); - - using (var stream = new MemoryStream()) - { - image.SaveAsBmp(stream); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs index 1f8961fcde..92190e653b 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -1,16 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp; namespace SixLabors.ImageSharp.Benchmarks.General { - /** - * Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + /* + Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | -------------------------------------------- |------ |---------:|---------:|---------:|-------:|---------:| 'Emulated 2D array access using flat array' | 32 | 224.2 ns | 4.739 ns | 13.75 ns | 0.65 | 0.07 | 'Array access using 2D array' | 32 | 346.6 ns | 9.225 ns | 26.91 ns | 1.00 | 0.00 | @@ -19,7 +19,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General * */ - public class Array2D { private float[] flatArray; @@ -34,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General public int Count { get; set; } public int Min { get; private set; } + public int Max { get; private set; } [GlobalSetup] @@ -65,11 +65,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General { for (int j = this.Min; j < this.Max; j++) { - ref float v = ref a[count * i + j]; + ref float v = ref a[(count * i) + j]; v = i * j; s += v; } } + return s; } @@ -87,6 +88,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } @@ -104,6 +106,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } @@ -121,7 +124,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs index c49c383eb8..41137e28be 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks.General [Params(4, 16, 32)] public int Count { get; set; } - byte[] source; + private byte[] source; - byte[] destination; + private byte[] destination; [GlobalSetup] public void SetUp() @@ -34,12 +34,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General { this.ReverseBytes(this.source, 0, this.Count); - //for (int i = 0; i < this.source.Length / 2; i++) - //{ - // byte tmp = this.source[i]; - // this.source[i] = this.source[this.source.Length - i - 1]; - // this.source[this.source.Length - i - 1] = tmp; - //} + /* + for (int i = 0; i < this.source.Length / 2; i++) + { + byte tmp = this.source[i]; + this.source[i] = this.source[this.source.Length - i - 1]; + this.source[this.source.Length - i - 1] = tmp; + }*/ } public void ReverseBytes(byte[] source, int index, int length) @@ -56,4 +57,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs index ea53959b6a..fc0b149c1f 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs index 404714a54b..9644cbc7d3 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -37,7 +40,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath return acc; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ClampUsingMathF(float x, float min, float max) { @@ -66,4 +68,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath // UsingMathF | 30.37 ns | 0.3764 ns | 0.3337 ns | 1.00 | // UsingBranching | 18.66 ns | 0.1043 ns | 0.0871 ns | 0.61 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs new file mode 100644 index 0000000000..145b98b0f4 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ + public class ClampVector4 + { + private readonly float min = -1.5f; + private readonly float max = 2.5f; + private static readonly float[] Values = { -10, -5, -3, -1.5f, -0.5f, 0f, 1f, 1.5f, 2.5f, 3, 10 }; + + [Benchmark(Baseline = true)] + public Vector4 UsingVectorClamp() + { + Vector4 acc = Vector4.Zero; + + for (int i = 0; i < Values.Length; i++) + { + acc += ClampUsingVectorClamp(Values[i], this.min, this.max); + } + + return acc; + } + + [Benchmark] + public Vector4 UsingVectorMinMax() + { + Vector4 acc = Vector4.Zero; + + for (int i = 0; i < Values.Length; i++) + { + acc += ClampUsingVectorMinMax(Values[i], this.min, this.max); + } + + return acc; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 ClampUsingVectorClamp(float x, float min, float max) + { + return Vector4.Clamp(new Vector4(x), new Vector4(min), new Vector4(max)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 ClampUsingVectorMinMax(float x, float min, float max) + { + return Vector4.Min(new Vector4(max), Vector4.Max(new Vector4(min), new Vector4(x))); + } + + // RESULTS + // | Method | Mean | Error | StdDev | Ratio | + // |------------------ |---------:|---------:|---------:|------:| + // | UsingVectorClamp | 75.21 ns | 1.572 ns | 4.057 ns | 1.00 | + // | UsingVectorMinMax | 15.35 ns | 0.356 ns | 0.789 ns | 0.20 | + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs index 94349b20b6..0ccde7a13a 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -19,4 +22,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath return ImageMaths.Modulo8(this.value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs index d5683673fe..e8cb8ca622 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -28,4 +31,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath // Standard | 1.2465 ns | 0.0093 ns | 0.0455 ns | 1.2423 ns | 1.00 | 0.00 | // Bitwise | 0.0265 ns | 0.0103 ns | 0.0515 ns | 0.0000 ns | 0.02 | 0.04 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs index 0f256fc781..b7eb01fcb5 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; @@ -9,7 +12,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath [Params(-1.333F, 1.333F)] public float X { get; set; } - [Benchmark(Baseline = true, Description = "Math.Pow 2")] public float MathPow() { diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs index 2c18b2972c..bb308d4805 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs @@ -1,17 +1,20 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { public class Round { - private const float input = .51F; + private const float Input = .51F; [Benchmark] - public int ConvertTo() => Convert.ToInt32(input); + public int ConvertTo() => Convert.ToInt32(Input); [Benchmark] - public int MathRound() => (int)Math.Round(input); + public int MathRound() => (int)Math.Round(Input); // Results 20th Jan 2019 // Method | Mean | Error | StdDev | Median | diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs index 2c325d184a..2afa8753f8 100644 --- a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -34,7 +34,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General [Params(10, 50, 100, 1000, 10000)] public int Count { get; set; } - [GlobalSetup] public void Setup() { @@ -74,7 +73,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); } - [Benchmark(Description = "Marshal.Copy()")] public unsafe void MarshalCopy() { diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs index b5f339fb37..6d7c3c4236 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs @@ -1,10 +1,13 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - interface ITestPixel + public interface ITestPixel where T : struct, ITestPixel { void FromRgba32(Rgba32 source); @@ -25,4 +28,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion void CopyToVector4(ref Vector4 dest); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs index 9f1b2721b4..55527da188 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; @@ -9,6 +10,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public abstract class PixelConversion_ConvertFromRgba32 @@ -16,23 +18,23 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion internal struct ConversionRunner where T : struct, ITestPixel { - public readonly T[] dest; + public readonly T[] Dest; - public readonly Rgba32[] source; + public readonly Rgba32[] Source; public ConversionRunner(int count) { - this.dest = new T[count]; - this.source = new Rgba32[count]; + this.Dest = new T[count]; + this.Source = new Rgba32[count]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunByRefConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -43,10 +45,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunByValConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -57,10 +59,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunFromBytesConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -69,22 +71,19 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } } - - internal ConversionRunner compatibleMemLayoutRunner; - internal ConversionRunner permutedRunnerRgbaToArgb; + internal ConversionRunner CompatibleMemLayoutRunner; - [Params( - 256, - 2048 - )] + internal ConversionRunner PermutedRunnerRgbaToArgb; + + [Params(256, 2048)] public int Count { get; set; } [GlobalSetup] public void Setup() { - this.compatibleMemLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunnerRgbaToArgb = new ConversionRunner(this.Count); + this.CompatibleMemLayoutRunner = new ConversionRunner(this.Count); + this.PermutedRunnerRgbaToArgb = new ConversionRunner(this.Count); } } @@ -93,26 +92,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark(Baseline = true)] public void ByRef() { - this.compatibleMemLayoutRunner.RunByRefConversion(); + this.CompatibleMemLayoutRunner.RunByRefConversion(); } [Benchmark] public void ByVal() { - this.compatibleMemLayoutRunner.RunByValConversion(); + this.CompatibleMemLayoutRunner.RunByValConversion(); } [Benchmark] public void FromBytes() { - this.compatibleMemLayoutRunner.RunFromBytesConversion(); + this.CompatibleMemLayoutRunner.RunFromBytesConversion(); } [Benchmark] public void Inline() { - ref Rgba32 sBase = ref this.compatibleMemLayoutRunner.source[0]; - ref Rgba32 dBase = ref Unsafe.As(ref this.compatibleMemLayoutRunner.dest[0]); + ref Rgba32 sBase = ref this.CompatibleMemLayoutRunner.Source[0]; + ref Rgba32 dBase = ref Unsafe.As(ref this.CompatibleMemLayoutRunner.Dest[0]); for (int i = 0; i < this.Count; i++) { @@ -120,12 +119,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ---------- |------ |---------:|---------:|---------:|-------:|---------:| - // ByRef | 256 | 128.5 ns | 1.217 ns | 1.138 ns | 1.00 | 0.00 | - // ByVal | 256 | 196.7 ns | 2.792 ns | 2.612 ns | 1.53 | 0.02 | - // FromBytes | 256 | 321.7 ns | 2.180 ns | 1.820 ns | 2.50 | 0.03 | - // Inline | 256 | 129.9 ns | 2.759 ns | 2.581 ns | 1.01 | 0.02 | + /* Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + ---------- |------ |---------:|---------:|---------:|-------:|---------:| + ByRef | 256 | 128.5 ns | 1.217 ns | 1.138 ns | 1.00 | 0.00 | + ByVal | 256 | 196.7 ns | 2.792 ns | 2.612 ns | 1.53 | 0.02 | + FromBytes | 256 | 321.7 ns | 2.180 ns | 1.820 ns | 2.50 | 0.03 | + Inline | 256 | 129.9 ns | 2.759 ns | 2.581 ns | 1.01 | 0.02 | */ } public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 @@ -133,26 +132,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark(Baseline = true)] public void ByRef() { - this.permutedRunnerRgbaToArgb.RunByRefConversion(); + this.PermutedRunnerRgbaToArgb.RunByRefConversion(); } [Benchmark] public void ByVal() { - this.permutedRunnerRgbaToArgb.RunByValConversion(); + this.PermutedRunnerRgbaToArgb.RunByValConversion(); } [Benchmark] public void FromBytes() { - this.permutedRunnerRgbaToArgb.RunFromBytesConversion(); + this.PermutedRunnerRgbaToArgb.RunFromBytesConversion(); } [Benchmark] public void InlineShuffle() { - ref Rgba32 sBase = ref this.permutedRunnerRgbaToArgb.source[0]; - ref TestArgb dBase = ref this.permutedRunnerRgbaToArgb.dest[0]; + ref Rgba32 sBase = ref this.PermutedRunnerRgbaToArgb.Source[0]; + ref TestArgb dBase = ref this.PermutedRunnerRgbaToArgb.Dest[0]; for (int i = 0; i < this.Count; i++) { @@ -169,8 +168,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark] public void PixelConverter_Rgba32_ToArgb32() { - ref uint sBase = ref Unsafe.As(ref this.permutedRunnerRgbaToArgb.source[0]); - ref uint dBase = ref Unsafe.As(ref this.permutedRunnerRgbaToArgb.dest[0]); + ref uint sBase = ref Unsafe.As(ref this.PermutedRunnerRgbaToArgb.Source[0]); + ref uint dBase = ref Unsafe.As(ref this.PermutedRunnerRgbaToArgb.Dest[0]); for (int i = 0; i < this.Count; i++) { @@ -182,8 +181,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark] public void PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer() { - Span source = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.source); - Span dest = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.dest); + Span source = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Source); + Span dest = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Dest); source.CopyTo(dest); ref uint dBase = ref MemoryMarshal.GetReference(dest); @@ -195,21 +194,23 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ---------------------------------------------------------- |------ |-----------:|-----------:|-----------:|-------:|---------:| - // ByRef | 256 | 328.7 ns | 6.6141 ns | 6.1868 ns | 1.00 | 0.00 | - // ByVal | 256 | 322.0 ns | 4.3541 ns | 4.0728 ns | 0.98 | 0.02 | - // FromBytes | 256 | 321.5 ns | 3.3499 ns | 3.1335 ns | 0.98 | 0.02 | - // InlineShuffle | 256 | 330.7 ns | 4.2525 ns | 3.9778 ns | 1.01 | 0.02 | - // PixelConverter_Rgba32_ToArgb32 | 256 | 167.4 ns | 0.6357 ns | 0.5309 ns | 0.51 | 0.01 | - // PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 256 | 196.6 ns | 0.8929 ns | 0.7915 ns | 0.60 | 0.01 | - // | | | | | | | - // ByRef | 2048 | 2,534.4 ns | 8.2947 ns | 6.9265 ns | 1.00 | 0.00 | - // ByVal | 2048 | 2,638.5 ns | 52.6843 ns | 70.3320 ns | 1.04 | 0.03 | - // FromBytes | 2048 | 2,517.2 ns | 40.8055 ns | 38.1695 ns | 0.99 | 0.01 | - // InlineShuffle | 2048 | 2,546.5 ns | 21.2506 ns | 19.8778 ns | 1.00 | 0.01 | - // PixelConverter_Rgba32_ToArgb32 | 2048 | 1,265.7 ns | 5.1397 ns | 4.5562 ns | 0.50 | 0.00 | - // PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 2048 | 1,410.3 ns | 11.1939 ns | 9.9231 ns | 0.56 | 0.00 |// + /* + RESULTS: + Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + ---------------------------------------------------------- |------ |-----------:|-----------:|-----------:|-------:|---------:| + ByRef | 256 | 328.7 ns | 6.6141 ns | 6.1868 ns | 1.00 | 0.00 | + ByVal | 256 | 322.0 ns | 4.3541 ns | 4.0728 ns | 0.98 | 0.02 | + FromBytes | 256 | 321.5 ns | 3.3499 ns | 3.1335 ns | 0.98 | 0.02 | + InlineShuffle | 256 | 330.7 ns | 4.2525 ns | 3.9778 ns | 1.01 | 0.02 | + PixelConverter_Rgba32_ToArgb32 | 256 | 167.4 ns | 0.6357 ns | 0.5309 ns | 0.51 | 0.01 | + PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 256 | 196.6 ns | 0.8929 ns | 0.7915 ns | 0.60 | 0.01 | + | | | | | | | + ByRef | 2048 | 2,534.4 ns | 8.2947 ns | 6.9265 ns | 1.00 | 0.00 | + ByVal | 2048 | 2,638.5 ns | 52.6843 ns | 70.3320 ns | 1.04 | 0.03 | + FromBytes | 2048 | 2,517.2 ns | 40.8055 ns | 38.1695 ns | 0.99 | 0.01 | + InlineShuffle | 2048 | 2,546.5 ns | 21.2506 ns | 19.8778 ns | 1.00 | 0.01 | + PixelConverter_Rgba32_ToArgb32 | 2048 | 1,265.7 ns | 5.1397 ns | 4.5562 ns | 0.50 | 0.00 | + PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 2048 | 1,410.3 ns | 11.1939 ns | 9.9231 ns | 0.56 | 0.00 | + */ } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs index d0c8a3045c..0b24276d33 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; @@ -8,12 +9,13 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertFromVector4 { [StructLayout(LayoutKind.Sequential)] - struct TestRgbaVector : ITestPixel + private struct TestRgbaVector : ITestPixel { private Vector4 v; @@ -39,13 +41,17 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } public void FromRgba32(Rgba32 source) => throw new System.NotImplementedException(); + public void FromRgba32(ref Rgba32 source) => throw new System.NotImplementedException(); + public void FromBytes(byte r, byte g, byte b, byte a) => throw new System.NotImplementedException(); + public Rgba32 ToRgba32() => throw new System.NotImplementedException(); + public void CopyToRgba32(ref Rgba32 dest) => throw new System.NotImplementedException(); } - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] dest; @@ -100,7 +106,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion this.nonVectorRunner = new ConversionRunner(this.Count); this.vectorRunner = new ConversionRunner(this.Count); } - + [Benchmark(Baseline = true)] public void VectorByRef() { @@ -124,7 +130,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { this.nonVectorRunner.RunByValConversion(); } - } /* @@ -135,8 +140,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion * VectorByVal | 32 | 24.5347 ns | 0.0771 ns | 1.04 | 0.01 | * NonVectorByRef | 32 | 59.0187 ns | 0.2114 ns | 2.49 | 0.01 | * NonVectorByVal | 32 | 58.7529 ns | 0.2545 ns | 2.48 | 0.02 | - * + * * !!! Conclusion !!! * We do not need by-ref version of ConvertFromVector4() stuff */ -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs index ea8b34c249..93a27a5554 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -8,14 +11,14 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { /// /// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? - /// 1. Rgba32 ToRgba32(); + /// 1. Rgba32 ToRgba32(); /// OR /// 2. void CopyToRgba32(ref Rgba32 dest); /// ? /// public class PixelConversion_ConvertToRgba32 { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -98,7 +101,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /* * Results: - * + * * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | * --------------- |------ |------------ |---------- |------- |-------------- | * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | @@ -106,4 +109,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | */ -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs index fff9ae9bc7..6a59e993b8 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -8,7 +11,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -110,4 +113,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // CompatibleCopyTo | 32 | 36.12 ns | 0.3596 ns | 0.3003 ns | 0.68 | 0.01 | // PermutedRetval | 32 | 303.61 ns | 5.1697 ns | 4.8358 ns | 5.72 | 0.09 | // PermutedCopyTo | 32 | 38.05 ns | 0.8053 ns | 1.2297 ns | 0.72 | 0.02 | -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs index 68a16b7919..80a2e80d22 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -7,7 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4 { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -78,4 +81,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // UseRetval | 32 | 109.0 ns | 1.202 ns | 1.125 ns | 1.00 | // UseCopyTo | 32 | 108.6 ns | 1.151 ns | 1.020 ns | 1.00 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs index c6daf0f1e2..699a4cf09d 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -7,7 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -92,4 +95,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // UseRetval | 32 | 120.2 ns | 1.560 ns | 1.383 ns | 1.00 | 0.00 | // UseCopyTo | 32 | 121.7 ns | 2.439 ns | 2.281 ns | 1.01 | 0.02 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs index 40893914e1..7acb3ecfef 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.NoInlining)] private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -74,7 +77,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - [Benchmark] public void Default_Group4() { @@ -98,7 +100,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d2, 1).FromRgba32(s3); } } - + [Benchmark] public void BitOps() { @@ -137,6 +139,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The argb value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToArgb32(uint packedRgba) { @@ -148,6 +151,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The bgra value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToBgra32(uint packedRgba) { @@ -173,4 +177,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // BitOps | 64 | 39.25 ns | 0.3266 ns | 0.2895 ns | 0.37 | // BitOps_GroupAsULong | 64 | 41.80 ns | 0.2227 ns | 0.2083 ns | 0.39 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs index cd0aed3c47..4c8b987b2f 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -10,8 +13,8 @@ using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - //[MonoJob] - //[RyuJitX64Job] + // [MonoJob] + // [RyuJitX64Job] public class PixelConversion_Rgba32_To_Bgra32 { private Rgba32[] source; @@ -19,19 +22,22 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion private Bgra32[] dest; [StructLayout(LayoutKind.Sequential)] - struct Tuple4OfUInt32 + private struct Tuple4OfUInt32 { - public uint V0, V1, V2, V3; + private uint v0; + private uint v1; + private uint v2; + private uint v3; public void ConvertMe() { - this.V0 = FromRgba32.ToBgra32(this.V0); - this.V1 = FromRgba32.ToBgra32(this.V1); - this.V2 = FromRgba32.ToBgra32(this.V2); - this.V3 = FromRgba32.ToBgra32(this.V3); + this.v0 = FromRgba32.ToBgra32(this.v0); + this.v1 = FromRgba32.ToBgra32(this.v1); + this.v2 = FromRgba32.ToBgra32(this.v2); + this.v3 = FromRgba32.ToBgra32(this.v3); } } - + [Params(64)] public int Count { get; set; } @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.NoInlining)] private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -81,7 +87,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion ref Rgba32 sBase = ref this.source[0]; ref Bgra32 dBase = ref this.dest[0]; - for (int i = 0; i < this.Count; i+=2) + for (int i = 0; i < this.Count; i += 2) { ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); Rgba32 s1 = Unsafe.Add(ref s0, 1); @@ -115,10 +121,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d2, 1).FromRgba32(s3); } } - + [MethodImpl(MethodImplOptions.NoInlining)] private static void Group4GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -141,13 +147,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Default_Group4_Generic() { Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); } - //[Benchmark] + // [Benchmark] public void Default_Group8() { ref Rgba32 sBase = ref this.source[0]; @@ -174,7 +180,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion ref Bgra32 d5 = ref Unsafe.Add(ref d4, 1); ref Bgra32 d6 = ref Unsafe.Add(ref d5, 1); - d0.FromRgba32(s0); d1.FromRgba32(s1); d2.FromRgba32(s2); @@ -214,7 +219,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Bitops_SingleTuple() { ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); @@ -225,11 +230,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Bitops_Simd() { - ref Octet.OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + ref Octet sBase = ref Unsafe.As>(ref this.source[0]); + ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); for (int i = 0; i < this.Count / 8; i++) { @@ -237,22 +242,24 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } +#pragma warning disable SA1132 // Do not combine fields [StructLayout(LayoutKind.Sequential)] - struct B + private struct B { - public uint tmp2, tmp5, tmp8, tmp11, tmp14, tmp17, tmp20, tmp23; + public uint Tmp2, Tmp5, Tmp8, Tmp11, Tmp14, Tmp17, Tmp20, Tmp23; } [StructLayout(LayoutKind.Sequential)] - struct C + private struct C { - public uint tmp3, tmp6, tmp9, tmp12, tmp15, tmp18, tmp21, tmp24; + public uint Tmp3, Tmp6, Tmp9, Tmp12, Tmp15, Tmp18, Tmp21, Tmp24; } +#pragma warning restore SA1132 // Do not combine fields [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BitopsSimdImpl(ref Octet.OfUInt32 s, ref Octet.OfUInt32 d) + private static void BitopsSimdImpl(ref Octet s, ref Octet d) { - Vector sVec = Unsafe.As>(ref s); + Vector sVec = Unsafe.As, Vector>(ref s); var aMask = new Vector(0xFF00FF00); var bMask = new Vector(0x00FF00FF); @@ -263,22 +270,22 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion C c = default; - c.tmp3 = (b.tmp2 << 16) | (b.tmp2 >> 16); - c.tmp6 = (b.tmp5 << 16) | (b.tmp5 >> 16); - c.tmp9 = (b.tmp8 << 16) | (b.tmp8 >> 16); - c.tmp12 = (b.tmp11 << 16) | (b.tmp11 >> 16); - c.tmp15 = (b.tmp14 << 16) | (b.tmp14 >> 16); - c.tmp18 = (b.tmp17 << 16) | (b.tmp17 >> 16); - c.tmp21 = (b.tmp20 << 16) | (b.tmp20 >> 16); - c.tmp24 = (b.tmp23 << 16) | (b.tmp23 >> 16); + c.Tmp3 = (b.Tmp2 << 16) | (b.Tmp2 >> 16); + c.Tmp6 = (b.Tmp5 << 16) | (b.Tmp5 >> 16); + c.Tmp9 = (b.Tmp8 << 16) | (b.Tmp8 >> 16); + c.Tmp12 = (b.Tmp11 << 16) | (b.Tmp11 >> 16); + c.Tmp15 = (b.Tmp14 << 16) | (b.Tmp14 >> 16); + c.Tmp18 = (b.Tmp17 << 16) | (b.Tmp17 >> 16); + c.Tmp21 = (b.Tmp20 << 16) | (b.Tmp20 >> 16); + c.Tmp24 = (b.Tmp23 << 16) | (b.Tmp23 >> 16); Vector cc = Unsafe.As>(ref c); Vector dd = aa + cc; - d = Unsafe.As, Octet.OfUInt32>(ref dd); + d = Unsafe.As, Octet>(ref dd); } - //[Benchmark] + // [Benchmark] public void BitOps_Group2() { ref uint sBase = ref Unsafe.As(ref this.source[0]); @@ -294,7 +301,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); } } - + [Benchmark] public void BitOps_GroupAsULong() { @@ -315,7 +322,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void BitOps_GroupAsULong_V2() { ref ulong sBase = ref Unsafe.As(ref this.source[0]); @@ -350,6 +357,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The argb value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToArgb32(uint packedRgba) { @@ -361,6 +369,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The bgra value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToBgra32(uint packedRgba) { @@ -376,7 +385,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // RESULTS: // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs index 76de794eca..4985206054 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,9 +10,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { [StructLayout(LayoutKind.Sequential)] - struct TestArgb : ITestPixel + public struct TestArgb : ITestPixel { - public byte A, R, G, B; + public byte A; + public byte R; + public byte G; + public byte B; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromRgba32(Rgba32 p) @@ -86,4 +92,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion dest.W = this.A; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs index 36d5f3e5b9..b325ec7c64 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,9 +10,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { [StructLayout(LayoutKind.Sequential)] - struct TestRgba : ITestPixel + public struct TestRgba : ITestPixel { - public byte R, G, B, A; + public byte R; + public byte G; + public byte B; + public byte A; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromRgba32(Rgba32 source) @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { - return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); + return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -68,4 +74,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion dest = tmp; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/StructCasting.cs b/tests/ImageSharp.Benchmarks/General/StructCasting.cs index bed68b54a1..ff89ad3ffe 100644 --- a/tests/ImageSharp.Benchmarks/General/StructCasting.cs +++ b/tests/ImageSharp.Benchmarks/General/StructCasting.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General diff --git a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs index 02bc5d843e..80f4041620 100644 --- a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs +++ b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; @@ -28,8 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General this.GetRandomFloat(), this.GetRandomFloat(), this.GetRandomFloat(), - this.GetRandomFloat() - ); + this.GetRandomFloat()); } [Benchmark(Baseline = true)] @@ -37,10 +39,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General { Vector4 p = this.parameter; - Vector4 x = p * A / B + p * C / D; - Vector4 y = p / A * B + p / C * D; - Vector4 z = Vector4.Min(p, A); - Vector4 w = Vector4.Max(p, B); + Vector4 x = (p * A / B) + (p * C / D); + Vector4 y = (p / A * B) + (p / C * D); + var z = Vector4.Min(p, A); + var w = Vector4.Max(p, B); return x + y + z + w; } @@ -49,10 +51,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General { Vector4 p = this.parameter; - Vector4 x = p * new Vector4(1.2f) / new Vector4(2.3f) + p * new Vector4(4.5f) / new Vector4(6.7f); - Vector4 y = p / new Vector4(1.2f) * new Vector4(2.3f) + p / new Vector4(4.5f) * new Vector4(6.7f); - Vector4 z = Vector4.Min(p, new Vector4(1.2f)); - Vector4 w = Vector4.Max(p, new Vector4(2.3f)); + Vector4 x = (p * new Vector4(1.2f) / new Vector4(2.3f)) + (p * new Vector4(4.5f) / new Vector4(6.7f)); + Vector4 y = (p / new Vector4(1.2f) * new Vector4(2.3f)) + (p / new Vector4(4.5f) * new Vector4(6.7f)); + var z = Vector4.Min(p, new Vector4(1.2f)); + var w = Vector4.Max(p, new Vector4(2.3f)); return x + y + z + w; } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs index 60bf615c56..41764b8160 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; @@ -24,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < this.InputSize; i++) { - this.input[i] = (uint) i; + this.input[i] = (uint)i; } } @@ -43,7 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { var v = new Vector(this.testValue); - for (int i = 0; i < this.input.Length; i+=Vector.Count) + for (int i = 0; i < this.input.Length; i += Vector.Count) { var a = new Vector(this.input, i); a = Vector.BitwiseOr(a, v); diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs index be9534f7d0..8d842a0f51 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; @@ -51,4 +54,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs index bfc8d3de38..f103867cd8 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; @@ -53,4 +56,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs index df09aa569a..30dddf483a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -1,10 +1,15 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; namespace ImageSharp.Benchmarks.General.Vectorization { +#pragma warning disable SA1649 // File name should match first type name public class DivFloat : SIMDBenchmarkBase.Divide +#pragma warning restore SA1649 // File name should match first type name { protected override float GetTestValue() => 42; @@ -53,7 +58,7 @@ namespace ImageSharp.Benchmarks.General.Vectorization { protected override short GetTestValue() => 42; - protected override Vector GetTestVector() => new Vector(new short[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}); + protected override Vector GetTestVector() => new Vector(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); [Benchmark(Baseline = true)] public void Standard() @@ -65,4 +70,4 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs index 79207a9ff3..61de537821 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs index d837556f7d..a800df405b 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs index 7a679c0009..5e9ffaae84 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -1,9 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; namespace ImageSharp.Benchmarks.General.Vectorization { +#pragma warning disable SA1649 // File name should match first type name public class MulUInt32 : SIMDBenchmarkBase.Multiply +#pragma warning restore SA1649 // File name should match first type name { protected override uint GetTestValue() => 42u; @@ -47,4 +52,4 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs index 23f13c89b7..cdc7cac2e8 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -56,4 +59,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization source *= new Vector4(w) { W = 1 }; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs index 19a1bcea45..dc921bc420 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using System.Runtime.InteropServices; @@ -15,22 +18,20 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization public int InputSize { get; set; } [StructLayout(LayoutKind.Explicit)] - struct UIntFloatUnion + private struct UIntFloatUnion { [FieldOffset(0)] - public float f; + public float F; [FieldOffset(0)] - public uint i; + public uint I; } - [GlobalSetup] public void Setup() { this.input = new uint[this.InputSize]; this.result = new float[this.InputSize]; - for (int i = 0; i < this.InputSize; i++) { this.input[i] = (uint)i; @@ -43,8 +44,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization UIntFloatUnion u = default; for (int i = 0; i < this.input.Length; i++) { - u.i = this.input[i]; - this.result[i] = u.f; + u.I = this.input[i]; + this.result[i] = u.F; } } @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < this.input.Length; i += Vector.Count) { var a = new Vector(this.input, i); - Vector b = Vector.AsVectorSingle(a); + var b = Vector.AsVectorSingle(a); b.CopyTo(this.result, i); } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs index 8a14f02451..8fa0b5cfcf 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using System.Runtime.CompilerServices; @@ -22,7 +25,7 @@ namespace ImageSharp.Benchmarks.General.Vectorization [Params(32)] public int InputSize { get; set; } - + [GlobalSetup] public virtual void Setup() { @@ -63,7 +66,5 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } - - } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs index 2c9f4289e3..3c79df494e 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using System.Runtime.CompilerServices; @@ -32,10 +35,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < n; i++) { - // union { float f; uint32_t i; } u; - // u.f = 32768.0f + x * (255.0f / 256.0f); - // return (uint8_t)u.i; - ref Vector df = ref Unsafe.Add(ref b, i); var vi = Vector.AsVectorUInt32(df); @@ -67,7 +66,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization Unsafe.Add(ref bf, i) = v; } } - + [Benchmark] public void StandardSimdFromInt() { @@ -87,7 +86,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } - [Benchmark] public void StandardSimdFromInt_RefCast() { diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs index 4d83dd4910..6d177588b4 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { using System; @@ -18,13 +21,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Params(64)] public int InputSize { get; set; } - + [GlobalSetup] public void Setup() { this.data = new float[this.InputSize]; this.testValue = 42; - + for (int i = 0; i < this.InputSize; i++) { this.data[i] = i; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index 2bc3af4c98..beac94269d 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -28,8 +31,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { const int N = Count / 8; - ref Octet.OfByte sBase = ref Unsafe.As(ref this.source[0]); - ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + ref Octet sBase = ref Unsafe.As>(ref this.source[0]); + ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); for (int i = 0; i < N; i++) { @@ -61,4 +64,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 14ad5635cd..f380d0a6a9 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,13 +1,17 @@ - + ImageSharp.Benchmarks Exe SixLabors.ImageSharp.Benchmarks - netcoreapp2.1;net472 - false + netcoreapp3.1;netcoreapp2.1;net472 false + + false + + + @@ -16,15 +20,29 @@ + + - + - - - + + + + + + + + + + + + + + + diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index ce4e16c446..8953228f97 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -13,7 +13,7 @@ using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.Benchmarks { - using CoreSize = SixLabors.Primitives.Size; + using CoreSize = SixLabors.ImageSharp.Size; public class PorterDuffBulkVsPixel : BenchmarkBase { @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks Span background, Span source, Span amount) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Benchmarks Span background, Span source, Span amount) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index 4fe7a365f3..8a5cccd68b 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -1,22 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - using System.Drawing; using System.Drawing.Drawing2D; - using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using CoreSize = SixLabors.Primitives.Size; +using SDRectangle = System.Drawing.Rectangle; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks { + [Config(typeof(Config.ShortClr))] public class Crop : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public Size CropSystemDrawing() + public SDSize CropSystemDrawing() { using (var source = new Bitmap(800, 800)) using (var destination = new Bitmap(100, 100)) @@ -25,19 +26,19 @@ namespace SixLabors.ImageSharp.Benchmarks graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - + graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + return destination.Size; } } [Benchmark(Description = "ImageSharp Crop")] - public CoreSize CropResizeCore() + public Size CropResizeCore() { using (var image = new Image(800, 800)) { image.Mutate(x => x.Crop(100, 100)); - return new CoreSize(image.Width, image.Height); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index b36b28ef33..7718e72159 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -10,8 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Processing; - - using CoreImage = ImageSharp.Image; + using CoreImage = SixLabors.ImageSharp.Image; public class DetectEdges : BenchmarkBase { @@ -51,4 +50,4 @@ namespace SixLabors.ImageSharp.Benchmarks this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs new file mode 100644 index 0000000000..e53661c731 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class Diffuse + { + [Benchmark] + public Size DoDiffuse() + { + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) + { + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); + + return image.Size(); + } + } + + [Benchmark] + public Size DoDither() + { + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) + { + image.Mutate(x => x.Dither()); + + return image.Size(); + } + } + } +} + +// #### 20th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-OJKYBT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-RZWLFP : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-NUYUQV : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:| +// | DoDiffuse | .NET 4.7.2 | 30.535 ms | 19.217 ms | 1.0534 ms | - | - | - | 26.25 KB | +// | DoDither | .NET 4.7.2 | 14.174 ms | 1.625 ms | 0.0891 ms | - | - | - | 31.38 KB | +// | DoDiffuse | .NET Core 2.1 | 15.984 ms | 3.686 ms | 0.2020 ms | - | - | - | 25.98 KB | +// | DoDither | .NET Core 2.1 | 8.646 ms | 1.635 ms | 0.0896 ms | - | - | - | 28.99 KB | +// | DoDiffuse | .NET Core 3.1 | 16.235 ms | 9.612 ms | 0.5269 ms | - | - | - | 25.96 KB | +// | DoDither | .NET Core 3.1 | 8.429 ms | 1.270 ms | 0.0696 ms | - | - | - | 31.61 KB | diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs index 3a47d016a4..711669b14e 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -10,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.White)) + using (var image = new Image(Configuration.Default, 400, 400, Color.White)) { image.Mutate(c => c.GaussianBlur()); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 172e243729..49a1bd5417 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing; @@ -14,8 +14,10 @@ using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Benchmarks { [Config(typeof(Config.ShortClr))] +#pragma warning disable SA1649 // File name should match first type name public abstract class ResizeBenchmarkBase - where TPixel : struct, IPixel +#pragma warning restore SA1649 // File name should match first type name + where TPixel : unmanaged, IPixel { protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); @@ -24,20 +26,18 @@ namespace SixLabors.ImageSharp.Benchmarks private Bitmap sourceBitmap; [Params("3032-400")] - public virtual string SourceToDest { get; set; } - + public virtual string SourceToDest { get; set; } + protected int SourceSize { get; private set; } protected int DestSize { get; private set; } - [GlobalSetup] public virtual void Setup() { string[] stuff = this.SourceToDest.Split('-'); this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture); this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture); - this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); } @@ -75,11 +75,13 @@ namespace SixLabors.ImageSharp.Benchmarks // Parallel cases have been disabled for fast benchmark execution. // Uncomment, if you are interested in parallel speedup - //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] - //public int ImageSharp_P4() => this.RunImageSharpResize(4); + /* + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] + public int ImageSharp_P4() => this.RunImageSharpResize(4); - //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] - //public int ImageSharp_P8() => this.RunImageSharpResize(8); + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] + public int ImageSharp_P8() => this.RunImageSharpResize(8); + */ protected int RunImageSharpResize(int maxDegreeOfParallelism) { @@ -110,9 +112,9 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // IterationCount=3 LaunchCount=1 WarmupCount=3 - // + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // // Method | Job | Runtime | SourceToDest | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032-400 | 120.11 ms | 1.435 ms | 0.0786 ms | 1.00 | 0.00 | - | - | - | 1638 B | @@ -157,9 +159,9 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // + // // IterationCount=3 LaunchCount=1 WarmupCount=3 - // + // // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032 | 400 | 119.01 ms | 18.513 ms | 1.0147 ms | 1.00 | - | - | - | 1638 B | @@ -185,7 +187,7 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // + // // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032 | 400 | 121.37 ms | 48.580 ms | 2.6628 ms | 1.00 | 0.00 | - | - | - | 2048 B | @@ -195,7 +197,6 @@ namespace SixLabors.ImageSharp.Benchmarks // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | } - public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) @@ -212,9 +213,9 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // + // // IterationCount=3 LaunchCount=1 WarmupCount=3 - // + // // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |----------- |--------- |---------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032 | 400 | 120.7 ms | 68.985 ms | 3.7813 ms | 1.00 | 0.00 | - | - | - | 1638 B | @@ -223,4 +224,4 @@ namespace SixLabors.ImageSharp.Benchmarks // SystemDrawing | Core | Core | 3032 | 400 | 118.3 ms | 6.899 ms | 0.3781 ms | 1.00 | 0.00 | - | - | - | 96 B | // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 122.4 ms | 15.069 ms | 0.8260 ms | 1.03 | 0.01 | - | - | - | 15712 B | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs index 69ff1549bd..0610079fe3 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Benchmarks.Samplers { @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoRotate() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Rotate(37.5F)); @@ -24,25 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// Nov 7 2018 -//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 -//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores -//.NET Core SDK = 2.1.403 - -// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT -// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 -// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - -//LaunchCount=1 TargetCount=3 WarmupCount=3 - -// #### BEFORE ####: -// Method | Runtime | Mean | Error | StdDev | Allocated | -//--------- |-------- |---------:|----------:|----------:|----------:| -// DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB | -// DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB | - -// #### AFTER ####: -//Method | Runtime | Mean | Error | StdDev | Allocated | -//--------- |-------- |---------:|---------:|---------:|----------:| -// DoRotate | Clr | 77.08 ms | 23.97 ms | 1.354 ms | 6 KB | -// DoRotate | Core | 40.36 ms | 47.43 ms | 2.680 ms | 4.36 KB | \ No newline at end of file +// #### 21th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-FKDHXC : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-ODABAZ : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:| +// | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 KB | +// | DoRotate | .NET Core 2.1 | 16.27 ms | 1.044 ms | 0.057 ms | - | - | - | 5.25 KB | +// | DoRotate | .NET Core 3.1 | 17.12 ms | 4.352 ms | 0.239 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs index 559e49704b..7b8ec83a5b 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Benchmarks.Samplers { @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoSkew() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Skew(20, 10)); @@ -24,25 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// Nov 7 2018 -//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 -//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores -//.NET Core SDK = 2.1.403 - -// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT -// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 -// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - -//LaunchCount=1 TargetCount=3 WarmupCount=3 - -// #### BEFORE ####: -//Method | Runtime | Mean | Error | StdDev | Allocated | -//------- |-------- |---------:|---------:|----------:|----------:| -// DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB | -// DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB | - -// #### AFTER ####: -//Method | Runtime | Mean | Error | StdDev | Allocated | -//------- |-------- |---------:|----------:|----------:|----------:| -// DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB | -// DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB | \ No newline at end of file +// #### 21th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-KTVRKR : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-EONWDB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| +// | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 KB | +// | DoSkew | .NET Core 2.1 | 12.13 ms | 2.256 ms | 0.124 ms | - | - | - | 5.21 KB | +// | DoSkew | .NET Core 3.1 | 12.83 ms | 1.442 ms | 0.079 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj deleted file mode 100644 index fc94668e11..0000000000 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - - SixLabors.ImageSharp.Sandbox46 - A cross-platform library for processing of image files written in C# - Exe - false - SixLabors.ImageSharp.Sandbox46 - win7-x64 - net472 - - - - - - - - - - - - - - - - - diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs deleted file mode 100644 index 93fe74076e..0000000000 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; -using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; - -namespace SixLabors.ImageSharp.Sandbox46 -{ - using System; - using SixLabors.ImageSharp.Tests.Formats.Jpg; - - using Xunit.Abstractions; - - public class Program - { - private class ConsoleOutput : ITestOutputHelper - { - public void WriteLine(string message) => Console.WriteLine(message); - - public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); - } - - /// - /// The main entry point. Useful for executing benchmarks and performance unit tests manually, - /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) - { - // RunJpegColorProfilingTests(); - - // RunDecodeJpegProfilingTests(); - // RunToVector4ProfilingTest(); - RunResizeProfilingTest(); - - Console.ReadLine(); - } - - private static void RunJpegColorProfilingTests() - { - new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false); - new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true); - } - - private static void RunResizeProfilingTest() - { - var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); - test.ResizeBicubic(4000, 4000); - } - - private static void RunToVector4ProfilingTest() - { - var tests = new PixelOperationsTests.Rgba32OperationsTests(new ConsoleOutput()); - tests.Benchmark_ToVector4(); - } - - private static void RunDecodeJpegProfilingTests() - { - Console.WriteLine("RunDecodeJpegProfilingTests..."); - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) - { - string fileName = (string)data[0]; - benchmarks.DecodeJpeg(fileName); - } - } - } -} diff --git a/tests/ImageSharp.Sandbox46/README.md b/tests/ImageSharp.Sandbox46/README.md deleted file mode 100644 index b05afb8538..0000000000 --- a/tests/ImageSharp.Sandbox46/README.md +++ /dev/null @@ -1,24 +0,0 @@ -## Purpose -This project aims to workaround certain .NET Core tooling issues in Visual Studio based developer workflow at the time of it's creation (January 2017): -- .NET Core Performance profiling is not possible neither with Visual Studio nor with JetBrains profilers -- ~~JetBrains Unit Test explorer does not work with .NET Core projects~~ - -## How does it work? -- By referencing .NET 4.5 dll-s created by net45 target's of ImageSharp projects. NOTE: These are not project references! -- By including test classes (and utility classes) of the `ImageSharp.Tests` project using MSBUILD `` -- Compiling `ImageSharp.Sandbox46` should trigger the compilation of ImageSharp subprojects using a manually defined solution dependencies - -## How to profile unit tests - -#### 1. With Visual Studio 2015 Test Runner -- **Do not** build `ImageSharp.Tests` -- Build `ImageSharp.Sandbox46` -- Use the [context menu in Test Explorer](https://adamprescott.net/2012/12/12/performance-profiling-for-unit-tests/) - -NOTE: -There was no *Profile test* option in my VS Professional. Maybe things were messed by VS2017 RC installation. [This post suggests](http://stackoverflow.com/questions/32034375/profiling-tests-in-visual-studio-community-2015) it's necessary to own Premium or Ultimate edition of Visual Studio to profile tests. - -#### 2. With JetBrains ReSharper Ultimate -- The `Sandbox46` project is no longer needed here. The classic `ImageSharp.Tests` project can be discovered by Unit Test Explorer. -- You can use [context menus](https://www.jetbrains.com/resharper/features/unit_testing.html) from your test class, or from unit Test Exporer/Unit Test Sessions windows. -![Context Menu](https://www.jetbrains.com/resharper/features/screenshots/100/unit_testing_profiling.png) \ No newline at end of file diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj new file mode 100644 index 0000000000..7c80316930 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -0,0 +1,31 @@ + + + + + ImageSharp.Tests.ProfilingSandbox + A cross-platform library for processing of image files written in C# + Exe + false + SixLabors.ImageSharp.Tests.ProfilingSandbox + win7-x64 + netcoreapp3.1;netcoreapp2.1;net472 + SixLabors.ImageSharp.Tests.ProfilingSandbox.Program + + false + + + + + + + + + + + + + + + + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs new file mode 100644 index 0000000000..a94d0ed83f --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Tests.Formats.Jpg; +using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; +using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; +using Xunit.Abstractions; + +// in this file, comments are used for disabling stuff for local execution +#pragma warning disable SA1515 +#pragma warning disable SA1512 + +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox +{ + public class Program + { + private class ConsoleOutput : ITestOutputHelper + { + public void WriteLine(string message) => Console.WriteLine(message); + + public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); + } + + /// + /// The main entry point. Useful for executing benchmarks and performance unit tests manually, + /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. + /// + /// + /// The arguments to pass to the program. + /// + public static void Main(string[] args) + { + // RunJpegColorProfilingTests(); + RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); + // RunResizeProfilingTest(); + + Console.ReadLine(); + } + + private static void RunJpegColorProfilingTests() + { + new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false); + new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true); + } + + private static void RunResizeProfilingTest() + { + var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); + test.ResizeBicubic(4000, 4000); + } + + private static void RunToVector4ProfilingTest() + { + var tests = new PixelOperationsTests.Rgba32OperationsTests(new ConsoleOutput()); + tests.Benchmark_ToVector4(); + } + + private static void RunDecodeJpegProfilingTests() + { + Console.WriteLine("RunDecodeJpegProfilingTests..."); + var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); + foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) + { + string fileName = (string)data[0]; + int executionCount = (int)data[1]; + benchmarks.DecodeJpeg(fileName, executionCount); + } + + Console.WriteLine("DONE."); + } + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/README.md b/tests/ImageSharp.Tests.ProfilingSandbox/README.md new file mode 100644 index 0000000000..43fdab9ef6 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/README.md @@ -0,0 +1,2 @@ +## ImageSharp.Tests.ProfilingSandbox +Helper project to run and profile unit tests or other "sandbox" code from a single .exe entry point. diff --git a/tests/ImageSharp.Sandbox46/app.config b/tests/ImageSharp.Tests.ProfilingSandbox/app.config similarity index 100% rename from tests/ImageSharp.Sandbox46/app.config rename to tests/ImageSharp.Tests.ProfilingSandbox/app.config diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 8d60338498..97731be94e 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -1,151 +1,163 @@ -// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Advanced { public class AdvancedImageExtensionsTests { - public class GetPixelMemory + public class GetPixelMemoryGroup { [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsOwned(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - // Act: - Memory memory = image0.GetPixelMemory(); + using Image image = provider.GetImage(); - // Assert: - Assert.Equal(image0.Width * image0.Height, memory.Length); - memory.Span.CopyTo(targetBuffer); + // Act: + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); - } - } + // Assert: + VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); } + [Theory] + [WithBlankImages(16, 16, PixelTypes.Rgba32)] + public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + Memory memory = memoryGroup.Single(); + + image.Mutate(c => c.Resize(8, 8)); + + Assert.False(memoryGroup.IsValid); + Assert.ThrowsAny(() => _ = memoryGroup.First()); + Assert.ThrowsAny(() => _ = memory.Span); + } [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32 | PixelTypes.Bgr24)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsConsumed(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Bgr24)] + public void ConsumedMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; - image0.GetPixelSpan().CopyTo(targetBuffer); + using Image image0 = provider.GetImage(); + var targetBuffer = new TPixel[image0.Width * image0.Height]; - var managerOfExternalMemory = new TestMemoryManager(targetBuffer); + Assert.True(image0.TryGetSinglePixelSpan(out Span sourceBuffer)); - Memory externalMemory = managerOfExternalMemory.Memory; + sourceBuffer.CopyTo(targetBuffer); - using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) - { - Memory internalMemory = image1.GetPixelMemory(); - Assert.Equal(targetBuffer.Length, internalMemory.Length); - Assert.True(Unsafe.AreSame(ref targetBuffer[0], ref internalMemory.Span[0])); + var managerOfExternalMemory = new TestMemoryManager(targetBuffer); - image0.ComparePixelBufferTo(internalMemory.Span); - } + Memory externalMemory = managerOfExternalMemory.Memory; - // Make sure externalMemory works after destruction: - image0.ComparePixelBufferTo(externalMemory.Span); + using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + { + VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); } + + // Make sure externalMemory works after destruction: + VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); } - } - [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowMemory(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) + private static void VerifyMemoryGroupDataMatchesTestPattern( + TestImageProvider provider, + IMemoryGroup memoryGroup, + Size size) + where TPixel : unmanaged, IPixel { - var targetBuffer = new TPixel[image.Width * image.Height]; + Assert.True(memoryGroup.IsValid); + Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); + Assert.True(memoryGroup.BufferLength % size.Width == 0); - // Act: - for (int y = 0; y < image.Height; y++) + int cnt = 0; + for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) { - Memory rowMemory = image.GetPixelRowMemory(y); - rowMemory.Span.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + int y = cnt / size.Width; + int x = cnt % size.Width; - // Assert: - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); + TPixel actual = memoryGroup.GetElementAt(i); + Assert.Equal(expected, actual); } } } [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowSpan(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + + using Image image = provider.GetImage(); + for (int y = 0; y < image.Height; y++) + { // Act: - for (int y = 0; y < image.Height; y++) - { - Span rowMemory = image.GetPixelRowSpan(y); - rowMemory.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + Memory rowMemory = image.GetPixelRowMemory(y); + Span span = rowMemory.Span; // Assert: - using (Image image1 = provider.GetImage()) + for (int x = 0; x < image.Width; x++) { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]); } } } - #pragma warning disable 0618 + [Theory] + [WithBasicTestPatternImages(16, 16, PixelTypes.Rgba32)] + public void GetPixelRowMemory_DestructiveMutate_ShouldInvalidateMemory(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Memory memory3 = image.GetPixelRowMemory(3); + Memory memory10 = image.GetPixelRowMemory(10); + + image.Mutate(c => c.Resize(8, 8)); + + Assert.ThrowsAny(() => _ = memory3.Span); + Assert.ThrowsAny(() => _ = memory10.Span); + } [Theory] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public unsafe void DangerousGetPinnableReference_CopyToBuffer(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + [WithBlankImages(100, 111, PixelTypes.Rgba32)] + [WithBlankImages(400, 600, PixelTypes.Rgba32)] + public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - ref byte source = ref Unsafe.As(ref targetBuffer[0]); - ref byte dest = ref Unsafe.As(ref image.DangerousGetPinnableReferenceToPixelBuffer()); - - fixed (byte* targetPtr = &source) - fixed (byte* pixelBasePtr = &dest) - { - uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf()); - Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes); - } + using Image image = provider.GetImage(); - image.ComparePixelBufferTo(targetBuffer); - } + Memory memory = image.GetPixelRowMemory(image.Height - 1); + Span span = image.GetPixelRowSpan(image.Height - 1); + + Assert.True(span == memory.Span); } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index fbd1c73f16..c658227aeb 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index 6d9b34ee95..c689431f33 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -3,9 +3,7 @@ using System; using System.Linq; - using SixLabors.ImageSharp.PixelFormats; - using Xunit; namespace SixLabors.ImageSharp.Tests @@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WithAlpha() { - Color c1 = Color.FromRgba(111, 222, 55, 255); + var c1 = Color.FromRgba(111, 222, 55, 255); Color c2 = c1.WithAlpha(0.5f); var expected = new Rgba32(111, 222, 55, 128); @@ -56,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests public void ToHex() { string expected = "ABCD1234"; - Color color = Color.FromHex(expected); + var color = Color.ParseHex(expected); string actual = color.ToHex(); Assert.Equal(expected, actual); @@ -66,14 +64,22 @@ namespace SixLabors.ImageSharp.Tests public void WebSafePalette_IsCorrect() { Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(ReferencePalette.WebSafeColors, actualPalette); + + for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WebSafeColors[i], actualPalette[i]); + } } [Fact] public void WernerPalette_IsCorrect() { Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(ReferencePalette.WernerColors, actualPalette); + + for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WernerColors[i], actualPalette[i]); + } } public class FromHex @@ -81,28 +87,134 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ShortHex() { - Assert.Equal(new Rgb24(255, 255, 255), (Rgb24) Color.FromHex("#fff")); - Assert.Equal(new Rgb24(255, 255, 255), (Rgb24) Color.FromHex("fff")); - Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32) Color.FromHex("000f")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("#fff")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("fff")); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)Color.ParseHex("000f")); + } + + [Fact] + public void TryShortHex() + { + Assert.True(Color.TryParseHex("#fff", out Color actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("fff", out actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("000f", out actual)); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)actual); } [Fact] public void LeadingPoundIsOptional() { - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24) Color.FromHex("#008080")); - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24) Color.FromHex("008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("#008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("008080")); } [Fact] public void ThrowsOnEmpty() { - Assert.Throws(() => Color.FromHex("")); + Assert.Throws(() => Color.ParseHex(string.Empty)); + } + + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.ParseHex("!")); } [Fact] public void ThrowsOnNull() { - Assert.Throws(() => Color.FromHex(null)); + Assert.Throws(() => Color.ParseHex(null)); + } + + [Fact] + public void FalseOnEmpty() + { + Assert.False(Color.TryParseHex(string.Empty, out Color _)); + } + + [Fact] + public void FalseOnInvalid() + { + Assert.False(Color.TryParseHex("!", out Color _)); + } + + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParseHex(null, out Color _)); + } + } + + public class FromString + { + [Fact] + public void ColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + Assert.Equal(expected, (Rgba32)Color.Parse(name)); + Assert.Equal(expected, (Rgba32)Color.Parse(name.ToLowerInvariant())); + Assert.Equal(expected, (Rgba32)Color.Parse(expected.ToHex())); + } + } + + [Fact] + public void TryColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + + Assert.True(Color.TryParse(name, out Color actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(name.ToLowerInvariant(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(expected.ToHex(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + } + } + + [Fact] + public void ThrowsOnEmpty() + { + Assert.Throws(() => Color.Parse(string.Empty)); + } + + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.Parse("!")); + } + + [Fact] + public void ThrowsOnNull() + { + Assert.Throws(() => Color.Parse(null)); + } + + [Fact] + public void FalseOnEmpty() + { + Assert.False(Color.TryParse(string.Empty, out Color _)); + } + + [Fact] + public void FalseOnInvalid() + { + Assert.False(Color.TryParse("!", out Color _)); + } + + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParse(null, out Color _)); } } } diff --git a/tests/ImageSharp.Tests/Color/ReferencePalette.cs b/tests/ImageSharp.Tests/Color/ReferencePalette.cs index 3c6e382c58..d8403e27e2 100644 --- a/tests/ImageSharp.Tests/Color/ReferencePalette.cs +++ b/tests/ImageSharp.Tests/Color/ReferencePalette.cs @@ -1,7 +1,8 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; namespace SixLabors.ImageSharp.Tests { @@ -10,268 +11,422 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// - public static readonly Rgba32[] WebSafeColors = + public static readonly Color[] WebSafeColors = { - Rgba32.AliceBlue, - Rgba32.AntiqueWhite, - Rgba32.Aqua, - Rgba32.Aquamarine, - Rgba32.Azure, - Rgba32.Beige, - Rgba32.Bisque, - Rgba32.Black, - Rgba32.BlanchedAlmond, - Rgba32.Blue, - Rgba32.BlueViolet, - Rgba32.Brown, - Rgba32.BurlyWood, - Rgba32.CadetBlue, - Rgba32.Chartreuse, - Rgba32.Chocolate, - Rgba32.Coral, - Rgba32.CornflowerBlue, - Rgba32.Cornsilk, - Rgba32.Crimson, - Rgba32.Cyan, - Rgba32.DarkBlue, - Rgba32.DarkCyan, - Rgba32.DarkGoldenrod, - Rgba32.DarkGray, - Rgba32.DarkGreen, - Rgba32.DarkKhaki, - Rgba32.DarkMagenta, - Rgba32.DarkOliveGreen, - Rgba32.DarkOrange, - Rgba32.DarkOrchid, - Rgba32.DarkRed, - Rgba32.DarkSalmon, - Rgba32.DarkSeaGreen, - Rgba32.DarkSlateBlue, - Rgba32.DarkSlateGray, - Rgba32.DarkTurquoise, - Rgba32.DarkViolet, - Rgba32.DeepPink, - Rgba32.DeepSkyBlue, - Rgba32.DimGray, - Rgba32.DodgerBlue, - Rgba32.Firebrick, - Rgba32.FloralWhite, - Rgba32.ForestGreen, - Rgba32.Fuchsia, - Rgba32.Gainsboro, - Rgba32.GhostWhite, - Rgba32.Gold, - Rgba32.Goldenrod, - Rgba32.Gray, - Rgba32.Green, - Rgba32.GreenYellow, - Rgba32.Honeydew, - Rgba32.HotPink, - Rgba32.IndianRed, - Rgba32.Indigo, - Rgba32.Ivory, - Rgba32.Khaki, - Rgba32.Lavender, - Rgba32.LavenderBlush, - Rgba32.LawnGreen, - Rgba32.LemonChiffon, - Rgba32.LightBlue, - Rgba32.LightCoral, - Rgba32.LightCyan, - Rgba32.LightGoldenrodYellow, - Rgba32.LightGray, - Rgba32.LightGreen, - Rgba32.LightPink, - Rgba32.LightSalmon, - Rgba32.LightSeaGreen, - Rgba32.LightSkyBlue, - Rgba32.LightSlateGray, - Rgba32.LightSteelBlue, - Rgba32.LightYellow, - Rgba32.Lime, - Rgba32.LimeGreen, - Rgba32.Linen, - Rgba32.Magenta, - Rgba32.Maroon, - Rgba32.MediumAquamarine, - Rgba32.MediumBlue, - Rgba32.MediumOrchid, - Rgba32.MediumPurple, - Rgba32.MediumSeaGreen, - Rgba32.MediumSlateBlue, - Rgba32.MediumSpringGreen, - Rgba32.MediumTurquoise, - Rgba32.MediumVioletRed, - Rgba32.MidnightBlue, - Rgba32.MintCream, - Rgba32.MistyRose, - Rgba32.Moccasin, - Rgba32.NavajoWhite, - Rgba32.Navy, - Rgba32.OldLace, - Rgba32.Olive, - Rgba32.OliveDrab, - Rgba32.Orange, - Rgba32.OrangeRed, - Rgba32.Orchid, - Rgba32.PaleGoldenrod, - Rgba32.PaleGreen, - Rgba32.PaleTurquoise, - Rgba32.PaleVioletRed, - Rgba32.PapayaWhip, - Rgba32.PeachPuff, - Rgba32.Peru, - Rgba32.Pink, - Rgba32.Plum, - Rgba32.PowderBlue, - Rgba32.Purple, - Rgba32.RebeccaPurple, - Rgba32.Red, - Rgba32.RosyBrown, - Rgba32.RoyalBlue, - Rgba32.SaddleBrown, - Rgba32.Salmon, - Rgba32.SandyBrown, - Rgba32.SeaGreen, - Rgba32.SeaShell, - Rgba32.Sienna, - Rgba32.Silver, - Rgba32.SkyBlue, - Rgba32.SlateBlue, - Rgba32.SlateGray, - Rgba32.Snow, - Rgba32.SpringGreen, - Rgba32.SteelBlue, - Rgba32.Tan, - Rgba32.Teal, - Rgba32.Thistle, - Rgba32.Tomato, - Rgba32.Transparent, - Rgba32.Turquoise, - Rgba32.Violet, - Rgba32.Wheat, - Rgba32.White, - Rgba32.WhiteSmoke, - Rgba32.Yellow, - Rgba32.YellowGreen + Color.AliceBlue, + Color.AntiqueWhite, + Color.Aqua, + Color.Aquamarine, + Color.Azure, + Color.Beige, + Color.Bisque, + Color.Black, + Color.BlanchedAlmond, + Color.Blue, + Color.BlueViolet, + Color.Brown, + Color.BurlyWood, + Color.CadetBlue, + Color.Chartreuse, + Color.Chocolate, + Color.Coral, + Color.CornflowerBlue, + Color.Cornsilk, + Color.Crimson, + Color.Cyan, + Color.DarkBlue, + Color.DarkCyan, + Color.DarkGoldenrod, + Color.DarkGray, + Color.DarkGreen, + Color.DarkKhaki, + Color.DarkMagenta, + Color.DarkOliveGreen, + Color.DarkOrange, + Color.DarkOrchid, + Color.DarkRed, + Color.DarkSalmon, + Color.DarkSeaGreen, + Color.DarkSlateBlue, + Color.DarkSlateGray, + Color.DarkTurquoise, + Color.DarkViolet, + Color.DeepPink, + Color.DeepSkyBlue, + Color.DimGray, + Color.DodgerBlue, + Color.Firebrick, + Color.FloralWhite, + Color.ForestGreen, + Color.Fuchsia, + Color.Gainsboro, + Color.GhostWhite, + Color.Gold, + Color.Goldenrod, + Color.Gray, + Color.Green, + Color.GreenYellow, + Color.Honeydew, + Color.HotPink, + Color.IndianRed, + Color.Indigo, + Color.Ivory, + Color.Khaki, + Color.Lavender, + Color.LavenderBlush, + Color.LawnGreen, + Color.LemonChiffon, + Color.LightBlue, + Color.LightCoral, + Color.LightCyan, + Color.LightGoldenrodYellow, + Color.LightGray, + Color.LightGreen, + Color.LightPink, + Color.LightSalmon, + Color.LightSeaGreen, + Color.LightSkyBlue, + Color.LightSlateGray, + Color.LightSteelBlue, + Color.LightYellow, + Color.Lime, + Color.LimeGreen, + Color.Linen, + Color.Magenta, + Color.Maroon, + Color.MediumAquamarine, + Color.MediumBlue, + Color.MediumOrchid, + Color.MediumPurple, + Color.MediumSeaGreen, + Color.MediumSlateBlue, + Color.MediumSpringGreen, + Color.MediumTurquoise, + Color.MediumVioletRed, + Color.MidnightBlue, + Color.MintCream, + Color.MistyRose, + Color.Moccasin, + Color.NavajoWhite, + Color.Navy, + Color.OldLace, + Color.Olive, + Color.OliveDrab, + Color.Orange, + Color.OrangeRed, + Color.Orchid, + Color.PaleGoldenrod, + Color.PaleGreen, + Color.PaleTurquoise, + Color.PaleVioletRed, + Color.PapayaWhip, + Color.PeachPuff, + Color.Peru, + Color.Pink, + Color.Plum, + Color.PowderBlue, + Color.Purple, + Color.RebeccaPurple, + Color.Red, + Color.RosyBrown, + Color.RoyalBlue, + Color.SaddleBrown, + Color.Salmon, + Color.SandyBrown, + Color.SeaGreen, + Color.SeaShell, + Color.Sienna, + Color.Silver, + Color.SkyBlue, + Color.SlateBlue, + Color.SlateGray, + Color.Snow, + Color.SpringGreen, + Color.SteelBlue, + Color.Tan, + Color.Teal, + Color.Thistle, + Color.Tomato, + Color.Transparent, + Color.Turquoise, + Color.Violet, + Color.Wheat, + Color.White, + Color.WhiteSmoke, + Color.Yellow, + Color.YellowGreen }; /// /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. /// The hex codes were collected and defined by Nicholas Rougeux /// - public static readonly Rgba32[] WernerColors = + public static readonly Color[] WernerColors = { - Rgba32.FromHex("#f1e9cd"), - Rgba32.FromHex("#f2e7cf"), - Rgba32.FromHex("#ece6d0"), - Rgba32.FromHex("#f2eacc"), - Rgba32.FromHex("#f3e9ca"), - Rgba32.FromHex("#f2ebcd"), - Rgba32.FromHex("#e6e1c9"), - Rgba32.FromHex("#e2ddc6"), - Rgba32.FromHex("#cbc8b7"), - Rgba32.FromHex("#bfbbb0"), - Rgba32.FromHex("#bebeb3"), - Rgba32.FromHex("#b7b5ac"), - Rgba32.FromHex("#bab191"), - Rgba32.FromHex("#9c9d9a"), - Rgba32.FromHex("#8a8d84"), - Rgba32.FromHex("#5b5c61"), - Rgba32.FromHex("#555152"), - Rgba32.FromHex("#413f44"), - Rgba32.FromHex("#454445"), - Rgba32.FromHex("#423937"), - Rgba32.FromHex("#433635"), - Rgba32.FromHex("#252024"), - Rgba32.FromHex("#241f20"), - Rgba32.FromHex("#281f3f"), - Rgba32.FromHex("#1c1949"), - Rgba32.FromHex("#4f638d"), - Rgba32.FromHex("#383867"), - Rgba32.FromHex("#5c6b8f"), - Rgba32.FromHex("#657abb"), - Rgba32.FromHex("#6f88af"), - Rgba32.FromHex("#7994b5"), - Rgba32.FromHex("#6fb5a8"), - Rgba32.FromHex("#719ba2"), - Rgba32.FromHex("#8aa1a6"), - Rgba32.FromHex("#d0d5d3"), - Rgba32.FromHex("#8590ae"), - Rgba32.FromHex("#3a2f52"), - Rgba32.FromHex("#39334a"), - Rgba32.FromHex("#6c6d94"), - Rgba32.FromHex("#584c77"), - Rgba32.FromHex("#533552"), - Rgba32.FromHex("#463759"), - Rgba32.FromHex("#bfbac0"), - Rgba32.FromHex("#77747f"), - Rgba32.FromHex("#4a475c"), - Rgba32.FromHex("#b8bfaf"), - Rgba32.FromHex("#b2b599"), - Rgba32.FromHex("#979c84"), - Rgba32.FromHex("#5d6161"), - Rgba32.FromHex("#61ac86"), - Rgba32.FromHex("#a4b6a7"), - Rgba32.FromHex("#adba98"), - Rgba32.FromHex("#93b778"), - Rgba32.FromHex("#7d8c55"), - Rgba32.FromHex("#33431e"), - Rgba32.FromHex("#7c8635"), - Rgba32.FromHex("#8e9849"), - Rgba32.FromHex("#c2c190"), - Rgba32.FromHex("#67765b"), - Rgba32.FromHex("#ab924b"), - Rgba32.FromHex("#c8c76f"), - Rgba32.FromHex("#ccc050"), - Rgba32.FromHex("#ebdd99"), - Rgba32.FromHex("#ab9649"), - Rgba32.FromHex("#dbc364"), - Rgba32.FromHex("#e6d058"), - Rgba32.FromHex("#ead665"), - Rgba32.FromHex("#d09b2c"), - Rgba32.FromHex("#a36629"), - Rgba32.FromHex("#a77d35"), - Rgba32.FromHex("#f0d696"), - Rgba32.FromHex("#d7c485"), - Rgba32.FromHex("#f1d28c"), - Rgba32.FromHex("#efcc83"), - Rgba32.FromHex("#f3daa7"), - Rgba32.FromHex("#dfa837"), - Rgba32.FromHex("#ebbc71"), - Rgba32.FromHex("#d17c3f"), - Rgba32.FromHex("#92462f"), - Rgba32.FromHex("#be7249"), - Rgba32.FromHex("#bb603c"), - Rgba32.FromHex("#c76b4a"), - Rgba32.FromHex("#a75536"), - Rgba32.FromHex("#b63e36"), - Rgba32.FromHex("#b5493a"), - Rgba32.FromHex("#cd6d57"), - Rgba32.FromHex("#711518"), - Rgba32.FromHex("#e9c49d"), - Rgba32.FromHex("#eedac3"), - Rgba32.FromHex("#eecfbf"), - Rgba32.FromHex("#ce536b"), - Rgba32.FromHex("#b74a70"), - Rgba32.FromHex("#b7757c"), - Rgba32.FromHex("#612741"), - Rgba32.FromHex("#7a4848"), - Rgba32.FromHex("#3f3033"), - Rgba32.FromHex("#8d746f"), - Rgba32.FromHex("#4d3635"), - Rgba32.FromHex("#6e3b31"), - Rgba32.FromHex("#864735"), - Rgba32.FromHex("#553d3a"), - Rgba32.FromHex("#613936"), - Rgba32.FromHex("#7a4b3a"), - Rgba32.FromHex("#946943"), - Rgba32.FromHex("#c39e6d"), - Rgba32.FromHex("#513e32"), - Rgba32.FromHex("#8b7859"), - Rgba32.FromHex("#9b856b"), - Rgba32.FromHex("#766051"), - Rgba32.FromHex("#453b32") + Color.ParseHex("#f1e9cd"), + Color.ParseHex("#f2e7cf"), + Color.ParseHex("#ece6d0"), + Color.ParseHex("#f2eacc"), + Color.ParseHex("#f3e9ca"), + Color.ParseHex("#f2ebcd"), + Color.ParseHex("#e6e1c9"), + Color.ParseHex("#e2ddc6"), + Color.ParseHex("#cbc8b7"), + Color.ParseHex("#bfbbb0"), + Color.ParseHex("#bebeb3"), + Color.ParseHex("#b7b5ac"), + Color.ParseHex("#bab191"), + Color.ParseHex("#9c9d9a"), + Color.ParseHex("#8a8d84"), + Color.ParseHex("#5b5c61"), + Color.ParseHex("#555152"), + Color.ParseHex("#413f44"), + Color.ParseHex("#454445"), + Color.ParseHex("#423937"), + Color.ParseHex("#433635"), + Color.ParseHex("#252024"), + Color.ParseHex("#241f20"), + Color.ParseHex("#281f3f"), + Color.ParseHex("#1c1949"), + Color.ParseHex("#4f638d"), + Color.ParseHex("#383867"), + Color.ParseHex("#5c6b8f"), + Color.ParseHex("#657abb"), + Color.ParseHex("#6f88af"), + Color.ParseHex("#7994b5"), + Color.ParseHex("#6fb5a8"), + Color.ParseHex("#719ba2"), + Color.ParseHex("#8aa1a6"), + Color.ParseHex("#d0d5d3"), + Color.ParseHex("#8590ae"), + Color.ParseHex("#3a2f52"), + Color.ParseHex("#39334a"), + Color.ParseHex("#6c6d94"), + Color.ParseHex("#584c77"), + Color.ParseHex("#533552"), + Color.ParseHex("#463759"), + Color.ParseHex("#bfbac0"), + Color.ParseHex("#77747f"), + Color.ParseHex("#4a475c"), + Color.ParseHex("#b8bfaf"), + Color.ParseHex("#b2b599"), + Color.ParseHex("#979c84"), + Color.ParseHex("#5d6161"), + Color.ParseHex("#61ac86"), + Color.ParseHex("#a4b6a7"), + Color.ParseHex("#adba98"), + Color.ParseHex("#93b778"), + Color.ParseHex("#7d8c55"), + Color.ParseHex("#33431e"), + Color.ParseHex("#7c8635"), + Color.ParseHex("#8e9849"), + Color.ParseHex("#c2c190"), + Color.ParseHex("#67765b"), + Color.ParseHex("#ab924b"), + Color.ParseHex("#c8c76f"), + Color.ParseHex("#ccc050"), + Color.ParseHex("#ebdd99"), + Color.ParseHex("#ab9649"), + Color.ParseHex("#dbc364"), + Color.ParseHex("#e6d058"), + Color.ParseHex("#ead665"), + Color.ParseHex("#d09b2c"), + Color.ParseHex("#a36629"), + Color.ParseHex("#a77d35"), + Color.ParseHex("#f0d696"), + Color.ParseHex("#d7c485"), + Color.ParseHex("#f1d28c"), + Color.ParseHex("#efcc83"), + Color.ParseHex("#f3daa7"), + Color.ParseHex("#dfa837"), + Color.ParseHex("#ebbc71"), + Color.ParseHex("#d17c3f"), + Color.ParseHex("#92462f"), + Color.ParseHex("#be7249"), + Color.ParseHex("#bb603c"), + Color.ParseHex("#c76b4a"), + Color.ParseHex("#a75536"), + Color.ParseHex("#b63e36"), + Color.ParseHex("#b5493a"), + Color.ParseHex("#cd6d57"), + Color.ParseHex("#711518"), + Color.ParseHex("#e9c49d"), + Color.ParseHex("#eedac3"), + Color.ParseHex("#eecfbf"), + Color.ParseHex("#ce536b"), + Color.ParseHex("#b74a70"), + Color.ParseHex("#b7757c"), + Color.ParseHex("#612741"), + Color.ParseHex("#7a4848"), + Color.ParseHex("#3f3033"), + Color.ParseHex("#8d746f"), + Color.ParseHex("#4d3635"), + Color.ParseHex("#6e3b31"), + Color.ParseHex("#864735"), + Color.ParseHex("#553d3a"), + Color.ParseHex("#613936"), + Color.ParseHex("#7a4b3a"), + Color.ParseHex("#946943"), + Color.ParseHex("#c39e6d"), + Color.ParseHex("#513e32"), + Color.ParseHex("#8b7859"), + Color.ParseHex("#9b856b"), + Color.ParseHex("#766051"), + Color.ParseHex("#453b32") }; + + public static readonly Dictionary ColorNames = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(Color.AliceBlue), Color.AliceBlue }, + { nameof(Color.AntiqueWhite), Color.AntiqueWhite }, + { nameof(Color.Aqua), Color.Aqua }, + { nameof(Color.Aquamarine), Color.Aquamarine }, + { nameof(Color.Azure), Color.Azure }, + { nameof(Color.Beige), Color.Beige }, + { nameof(Color.Bisque), Color.Bisque }, + { nameof(Color.Black), Color.Black }, + { nameof(Color.BlanchedAlmond), Color.BlanchedAlmond }, + { nameof(Color.Blue), Color.Blue }, + { nameof(Color.BlueViolet), Color.BlueViolet }, + { nameof(Color.Brown), Color.Brown }, + { nameof(Color.BurlyWood), Color.BurlyWood }, + { nameof(Color.CadetBlue), Color.CadetBlue }, + { nameof(Color.Chartreuse), Color.Chartreuse }, + { nameof(Color.Chocolate), Color.Chocolate }, + { nameof(Color.Coral), Color.Coral }, + { nameof(Color.CornflowerBlue), Color.CornflowerBlue }, + { nameof(Color.Cornsilk), Color.Cornsilk }, + { nameof(Color.Crimson), Color.Crimson }, + { nameof(Color.Cyan), Color.Cyan }, + { nameof(Color.DarkBlue), Color.DarkBlue }, + { nameof(Color.DarkCyan), Color.DarkCyan }, + { nameof(Color.DarkGoldenrod), Color.DarkGoldenrod }, + { nameof(Color.DarkGray), Color.DarkGray }, + { nameof(Color.DarkGreen), Color.DarkGreen }, + { nameof(Color.DarkGrey), Color.DarkGrey }, + { nameof(Color.DarkKhaki), Color.DarkKhaki }, + { nameof(Color.DarkMagenta), Color.DarkMagenta }, + { nameof(Color.DarkOliveGreen), Color.DarkOliveGreen }, + { nameof(Color.DarkOrange), Color.DarkOrange }, + { nameof(Color.DarkOrchid), Color.DarkOrchid }, + { nameof(Color.DarkRed), Color.DarkRed }, + { nameof(Color.DarkSalmon), Color.DarkSalmon }, + { nameof(Color.DarkSeaGreen), Color.DarkSeaGreen }, + { nameof(Color.DarkSlateBlue), Color.DarkSlateBlue }, + { nameof(Color.DarkSlateGray), Color.DarkSlateGray }, + { nameof(Color.DarkSlateGrey), Color.DarkSlateGrey }, + { nameof(Color.DarkTurquoise), Color.DarkTurquoise }, + { nameof(Color.DarkViolet), Color.DarkViolet }, + { nameof(Color.DeepPink), Color.DeepPink }, + { nameof(Color.DeepSkyBlue), Color.DeepSkyBlue }, + { nameof(Color.DimGray), Color.DimGray }, + { nameof(Color.DimGrey), Color.DimGrey }, + { nameof(Color.DodgerBlue), Color.DodgerBlue }, + { nameof(Color.Firebrick), Color.Firebrick }, + { nameof(Color.FloralWhite), Color.FloralWhite }, + { nameof(Color.ForestGreen), Color.ForestGreen }, + { nameof(Color.Fuchsia), Color.Fuchsia }, + { nameof(Color.Gainsboro), Color.Gainsboro }, + { nameof(Color.GhostWhite), Color.GhostWhite }, + { nameof(Color.Gold), Color.Gold }, + { nameof(Color.Goldenrod), Color.Goldenrod }, + { nameof(Color.Gray), Color.Gray }, + { nameof(Color.Green), Color.Green }, + { nameof(Color.GreenYellow), Color.GreenYellow }, + { nameof(Color.Grey), Color.Grey }, + { nameof(Color.Honeydew), Color.Honeydew }, + { nameof(Color.HotPink), Color.HotPink }, + { nameof(Color.IndianRed), Color.IndianRed }, + { nameof(Color.Indigo), Color.Indigo }, + { nameof(Color.Ivory), Color.Ivory }, + { nameof(Color.Khaki), Color.Khaki }, + { nameof(Color.Lavender), Color.Lavender }, + { nameof(Color.LavenderBlush), Color.LavenderBlush }, + { nameof(Color.LawnGreen), Color.LawnGreen }, + { nameof(Color.LemonChiffon), Color.LemonChiffon }, + { nameof(Color.LightBlue), Color.LightBlue }, + { nameof(Color.LightCoral), Color.LightCoral }, + { nameof(Color.LightCyan), Color.LightCyan }, + { nameof(Color.LightGoldenrodYellow), Color.LightGoldenrodYellow }, + { nameof(Color.LightGray), Color.LightGray }, + { nameof(Color.LightGreen), Color.LightGreen }, + { nameof(Color.LightGrey), Color.LightGrey }, + { nameof(Color.LightPink), Color.LightPink }, + { nameof(Color.LightSalmon), Color.LightSalmon }, + { nameof(Color.LightSeaGreen), Color.LightSeaGreen }, + { nameof(Color.LightSkyBlue), Color.LightSkyBlue }, + { nameof(Color.LightSlateGray), Color.LightSlateGray }, + { nameof(Color.LightSlateGrey), Color.LightSlateGrey }, + { nameof(Color.LightSteelBlue), Color.LightSteelBlue }, + { nameof(Color.LightYellow), Color.LightYellow }, + { nameof(Color.Lime), Color.Lime }, + { nameof(Color.LimeGreen), Color.LimeGreen }, + { nameof(Color.Linen), Color.Linen }, + { nameof(Color.Magenta), Color.Magenta }, + { nameof(Color.Maroon), Color.Maroon }, + { nameof(Color.MediumAquamarine), Color.MediumAquamarine }, + { nameof(Color.MediumBlue), Color.MediumBlue }, + { nameof(Color.MediumOrchid), Color.MediumOrchid }, + { nameof(Color.MediumPurple), Color.MediumPurple }, + { nameof(Color.MediumSeaGreen), Color.MediumSeaGreen }, + { nameof(Color.MediumSlateBlue), Color.MediumSlateBlue }, + { nameof(Color.MediumSpringGreen), Color.MediumSpringGreen }, + { nameof(Color.MediumTurquoise), Color.MediumTurquoise }, + { nameof(Color.MediumVioletRed), Color.MediumVioletRed }, + { nameof(Color.MidnightBlue), Color.MidnightBlue }, + { nameof(Color.MintCream), Color.MintCream }, + { nameof(Color.MistyRose), Color.MistyRose }, + { nameof(Color.Moccasin), Color.Moccasin }, + { nameof(Color.NavajoWhite), Color.NavajoWhite }, + { nameof(Color.Navy), Color.Navy }, + { nameof(Color.OldLace), Color.OldLace }, + { nameof(Color.Olive), Color.Olive }, + { nameof(Color.OliveDrab), Color.OliveDrab }, + { nameof(Color.Orange), Color.Orange }, + { nameof(Color.OrangeRed), Color.OrangeRed }, + { nameof(Color.Orchid), Color.Orchid }, + { nameof(Color.PaleGoldenrod), Color.PaleGoldenrod }, + { nameof(Color.PaleGreen), Color.PaleGreen }, + { nameof(Color.PaleTurquoise), Color.PaleTurquoise }, + { nameof(Color.PaleVioletRed), Color.PaleVioletRed }, + { nameof(Color.PapayaWhip), Color.PapayaWhip }, + { nameof(Color.PeachPuff), Color.PeachPuff }, + { nameof(Color.Peru), Color.Peru }, + { nameof(Color.Pink), Color.Pink }, + { nameof(Color.Plum), Color.Plum }, + { nameof(Color.PowderBlue), Color.PowderBlue }, + { nameof(Color.Purple), Color.Purple }, + { nameof(Color.RebeccaPurple), Color.RebeccaPurple }, + { nameof(Color.Red), Color.Red }, + { nameof(Color.RosyBrown), Color.RosyBrown }, + { nameof(Color.RoyalBlue), Color.RoyalBlue }, + { nameof(Color.SaddleBrown), Color.SaddleBrown }, + { nameof(Color.Salmon), Color.Salmon }, + { nameof(Color.SandyBrown), Color.SandyBrown }, + { nameof(Color.SeaGreen), Color.SeaGreen }, + { nameof(Color.SeaShell), Color.SeaShell }, + { nameof(Color.Sienna), Color.Sienna }, + { nameof(Color.Silver), Color.Silver }, + { nameof(Color.SkyBlue), Color.SkyBlue }, + { nameof(Color.SlateBlue), Color.SlateBlue }, + { nameof(Color.SlateGray), Color.SlateGray }, + { nameof(Color.SlateGrey), Color.SlateGrey }, + { nameof(Color.Snow), Color.Snow }, + { nameof(Color.SpringGreen), Color.SpringGreen }, + { nameof(Color.SteelBlue), Color.SteelBlue }, + { nameof(Color.Tan), Color.Tan }, + { nameof(Color.Teal), Color.Teal }, + { nameof(Color.Thistle), Color.Thistle }, + { nameof(Color.Tomato), Color.Tomato }, + { nameof(Color.Transparent), Color.Transparent }, + { nameof(Color.Turquoise), Color.Turquoise }, + { nameof(Color.Violet), Color.Violet }, + { nameof(Color.Wheat), Color.Wheat }, + { nameof(Color.White), Color.White }, + { nameof(Color.WhiteSmoke), Color.WhiteSmoke }, + { nameof(Color.Yellow), Color.Yellow }, + { nameof(Color.YellowGreen), Color.YellowGreen } + }; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs index dbc07b916e..4bba0ab039 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -32,15 +32,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new CieLab(Vector3.One); Assert.True(default(CieLab) == default(CieLab)); - Assert.True(default(CieLab) != new CieLab(1, 0, 1)); - Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.True(new CieLab(1, 0, 1) != default(CieLab)); + Assert.False(new CieLab(1, 0, 1) == default(CieLab)); Assert.Equal(default(CieLab), default(CieLab)); Assert.Equal(new CieLab(1, 0, 1), new CieLab(1, 0, 1)); Assert.Equal(new CieLab(Vector3.One), new CieLab(Vector3.One)); Assert.False(x.Equals(y)); - Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.False(new CieLab(1, 0, 1) == default(CieLab)); Assert.False(x.Equals((object)y)); Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs index 42ace9dbed..418893f350 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; namespace SixLabors.ImageSharp.Tests.Colorspaces @@ -29,15 +29,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new CieXyChromaticityCoordinates(1, 1); Assert.True(default(CieXyChromaticityCoordinates) == default(CieXyChromaticityCoordinates)); - Assert.True(default(CieXyChromaticityCoordinates) != new CieXyChromaticityCoordinates(1, 0)); - Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.True(new CieXyChromaticityCoordinates(1, 0) != default(CieXyChromaticityCoordinates)); + Assert.False(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); Assert.Equal(default(CieXyChromaticityCoordinates), default(CieXyChromaticityCoordinates)); Assert.Equal(new CieXyChromaticityCoordinates(1, 0), new CieXyChromaticityCoordinates(1, 0)); Assert.Equal(new CieXyChromaticityCoordinates(1, 1), new CieXyChromaticityCoordinates(1, 1)); Assert.False(x.Equals(y)); - Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.False(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); Assert.False(x.Equals((object)y)); Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs index 7bf84dd0af..2046cdfdc5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.ColorSpaces.Conversion; namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion IEqualityComparer, IEqualityComparer { - private readonly float Epsilon; + private readonly float epsilon; /// /// Initializes a new instance of the class. /// /// The comparison error difference epsilon to use. - public ApproximateColorSpaceComparer(float epsilon = 1F) => this.Epsilon = epsilon; + public ApproximateColorSpaceComparer(float epsilon = 1F) => this.epsilon = epsilon; /// public bool Equals(Rgb x, Rgb y) @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion private bool Equals(float x, float y) { float d = x - y; - return d >= -this.Epsilon && d <= this.Epsilon; + return d >= -this.epsilon && d <= this.epsilon; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs index 38c0c21bc9..50d831fd90 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -92,4 +92,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs index 96628977fe..471610eba0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -80,4 +80,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs index f7dc365b81..e007658328 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -76,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs index a65f618835..c5af017889 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -69,11 +69,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs index 6829c62b50..e14d02faf4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -87,11 +87,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs index 3b41204f7c..5566ce1b4c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs index bfc0d2ecf1..f130bb9470 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs index f11b17fff3..9e0af62eec 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs index de2329c2ec..68fe54b517 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs index 3a1bd10c41..7c3e66f528 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs index f3881f10f7..d42322336c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs index 644f4577bf..8223ffdbce 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs index 41b9dba091..e300049df0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs index 5b36beaab9..1c343afa29 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs index da77378759..9a3cb8b010 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs index 96d14c98a6..9e46024755 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs index 0339730945..71b41e6cab 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs index fb0e06e6bb..4737ba59f8 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs index 5bbcd90875..1193ccaa19 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs index 1ee84ef2e5..b1342c80c4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs index 77f0c69699..42f00c51e0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,17 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs index 24e134d732..f123617731 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,17 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs index cd1c9f2c3e..eda5db125f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs index 8112f6a198..47f780789b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs index 9ea890f101..d6d59ec076 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs index 326777f3c6..104b1f4b22 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; using Xunit; namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion @@ -178,4 +177,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Assert.Equal(expected, actual, ColorSpaceComparer); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs index 8b1fed84c2..8f9fef5e9e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs @@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion var input = new Rgb(r, g, b); var expected = new Hsl(h, s, l); - Span inputSpan = new Rgb[5]; inputSpan.Fill(input); diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs index b1427f4d5f..bd870b01ac 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,8 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); public static readonly TheoryData WhitePoints = new TheoryData { - {CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint}, - {CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint} + { CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint }, + { CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint } }; [Theory] diff --git a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs index 95261e1d98..a657098f57 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new HunterLab(Vector3.One); Assert.True(default(HunterLab) == default(HunterLab)); - Assert.True(default(HunterLab) != new HunterLab(1, 0, 1)); - Assert.False(default(HunterLab) == new HunterLab(1, 0, 1)); + Assert.True(new HunterLab(1, 0, 1) != default(HunterLab)); + Assert.False(new HunterLab(1, 0, 1) == default(HunterLab)); Assert.Equal(default(HunterLab), default(HunterLab)); Assert.Equal(new HunterLab(1, 0, 1), new HunterLab(1, 0, 1)); Assert.Equal(new HunterLab(Vector3.One), new HunterLab(Vector3.One)); @@ -42,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs index 1b0939dc5c..f0c1471e0c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces const float l = 75F; const float m = -64F; const float s = 87F; - var Lms = new Lms(l, m, s); + var lms = new Lms(l, m, s); - Assert.Equal(l, Lms.L); - Assert.Equal(m, Lms.M); - Assert.Equal(s, Lms.S); + Assert.Equal(l, lms.L); + Assert.Equal(m, lms.M); + Assert.Equal(s, lms.S); } [Fact] @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new Lms(Vector3.One); Assert.True(default(Lms) == default(Lms)); - Assert.True(default(Lms) != new Lms(1, 0, 1)); - Assert.False(default(Lms) == new Lms(1, 0, 1)); + Assert.True(new Lms(1, 0, 1) != default(Lms)); + Assert.False(new Lms(1, 0, 1) == default(Lms)); Assert.Equal(default(Lms), default(Lms)); Assert.Equal(new Lms(1, 0, 1), new Lms(1, 0, 1)); Assert.Equal(new Lms(Vector3.One), new Lms(Vector3.One)); @@ -42,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs index 5249b709b1..df96599945 100644 --- a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs @@ -1,66 +1,65 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; namespace SixLabors.ImageSharp.Tests.Colorspaces { public class StringRepresentationTests { - private static readonly Vector3 one = new Vector3(1); - private static readonly Vector3 zero = new Vector3(0); - private static readonly Vector3 random = new Vector3(42.4F, 94.5F, 83.4F); + private static readonly Vector3 One = new Vector3(1); + private static readonly Vector3 Zero = new Vector3(0); + private static readonly Vector3 Random = new Vector3(42.4F, 94.5F, 83.4F); public static readonly TheoryData TestData = new TheoryData { - { new CieLab(zero), "CieLab(0, 0, 0)" }, - { new CieLch(zero), "CieLch(0, 0, 0)" }, - { new CieLchuv(zero), "CieLchuv(0, 0, 0)" }, - { new CieLuv(zero), "CieLuv(0, 0, 0)" }, - { new CieXyz(zero), "CieXyz(0, 0, 0)" }, - { new CieXyy(zero), "CieXyy(0, 0, 0)" }, - { new HunterLab(zero), "HunterLab(0, 0, 0)" }, - { new Lms(zero), "Lms(0, 0, 0)" }, - { new LinearRgb(zero), "LinearRgb(0, 0, 0)" }, - { new Rgb(zero), "Rgb(0, 0, 0)" }, - { new Hsl(zero), "Hsl(0, 0, 0)" }, - { new Hsv(zero), "Hsv(0, 0, 0)" }, - { new YCbCr(zero), "YCbCr(0, 0, 0)" }, - - { new CieLab(one), "CieLab(1, 1, 1)" }, - { new CieLch(one), "CieLch(1, 1, 1)" }, - { new CieLchuv(one), "CieLchuv(1, 1, 1)" }, - { new CieLuv(one), "CieLuv(1, 1, 1)" }, - { new CieXyz(one), "CieXyz(1, 1, 1)" }, - { new CieXyy(one), "CieXyy(1, 1, 1)" }, - { new HunterLab(one), "HunterLab(1, 1, 1)" }, - { new Lms(one), "Lms(1, 1, 1)" }, - { new LinearRgb(one), "LinearRgb(1, 1, 1)" }, - { new Rgb(one), "Rgb(1, 1, 1)" }, - { new Hsl(one), "Hsl(1, 1, 1)" }, - { new Hsv(one), "Hsv(1, 1, 1)" }, - { new YCbCr(one), "YCbCr(1, 1, 1)" }, - { new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)"}, - - { new CieLab(random), "CieLab(42.4, 94.5, 83.4)" }, - { new CieLch(random), "CieLch(42.4, 94.5, 83.4)" }, - { new CieLchuv(random), "CieLchuv(42.4, 94.5, 83.4)" }, - { new CieLuv(random), "CieLuv(42.4, 94.5, 83.4)" }, - { new CieXyz(random), "CieXyz(42.4, 94.5, 83.4)" }, - { new CieXyy(random), "CieXyy(42.4, 94.5, 83.4)" }, - { new HunterLab(random), "HunterLab(42.4, 94.5, 83.4)" }, - { new Lms(random), "Lms(42.4, 94.5, 83.4)" }, - { new LinearRgb(random), "LinearRgb(1, 1, 1)" }, // clamping to 1 is expected - { new Rgb(random), "Rgb(1, 1, 1)" }, // clamping to 1 is expected - { new Hsl(random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected - { new Hsv(random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected - { new YCbCr(random), "YCbCr(42.4, 94.5, 83.4)" }, - }; + { new CieLab(Zero), "CieLab(0, 0, 0)" }, + { new CieLch(Zero), "CieLch(0, 0, 0)" }, + { new CieLchuv(Zero), "CieLchuv(0, 0, 0)" }, + { new CieLuv(Zero), "CieLuv(0, 0, 0)" }, + { new CieXyz(Zero), "CieXyz(0, 0, 0)" }, + { new CieXyy(Zero), "CieXyy(0, 0, 0)" }, + { new HunterLab(Zero), "HunterLab(0, 0, 0)" }, + { new Lms(Zero), "Lms(0, 0, 0)" }, + { new LinearRgb(Zero), "LinearRgb(0, 0, 0)" }, + { new Rgb(Zero), "Rgb(0, 0, 0)" }, + { new Hsl(Zero), "Hsl(0, 0, 0)" }, + { new Hsv(Zero), "Hsv(0, 0, 0)" }, + { new YCbCr(Zero), "YCbCr(0, 0, 0)" }, + { new CieLab(One), "CieLab(1, 1, 1)" }, + { new CieLch(One), "CieLch(1, 1, 1)" }, + { new CieLchuv(One), "CieLchuv(1, 1, 1)" }, + { new CieLuv(One), "CieLuv(1, 1, 1)" }, + { new CieXyz(One), "CieXyz(1, 1, 1)" }, + { new CieXyy(One), "CieXyy(1, 1, 1)" }, + { new HunterLab(One), "HunterLab(1, 1, 1)" }, + { new Lms(One), "Lms(1, 1, 1)" }, + { new LinearRgb(One), "LinearRgb(1, 1, 1)" }, + { new Rgb(One), "Rgb(1, 1, 1)" }, + { new Hsl(One), "Hsl(1, 1, 1)" }, + { new Hsv(One), "Hsv(1, 1, 1)" }, + { new YCbCr(One), "YCbCr(1, 1, 1)" }, + { new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)" }, + { new CieLab(Random), "CieLab(42.4, 94.5, 83.4)" }, + { new CieLch(Random), "CieLch(42.4, 94.5, 83.4)" }, + { new CieLchuv(Random), "CieLchuv(42.4, 94.5, 83.4)" }, + { new CieLuv(Random), "CieLuv(42.4, 94.5, 83.4)" }, + { new CieXyz(Random), "CieXyz(42.4, 94.5, 83.4)" }, + { new CieXyy(Random), "CieXyy(42.4, 94.5, 83.4)" }, + { new HunterLab(Random), "HunterLab(42.4, 94.5, 83.4)" }, + { new Lms(Random), "Lms(42.4, 94.5, 83.4)" }, + { new LinearRgb(Random), "LinearRgb(1, 1, 1)" }, // clamping to 1 is expected + { new Rgb(Random), "Rgb(1, 1, 1)" }, // clamping to 1 is expected + { new Hsl(Random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected + { new Hsv(Random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected + { new YCbCr(Random), "YCbCr(42.4, 94.5, 83.4)" }, + }; [Theory] [MemberData(nameof(TestData))] public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString()); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs index e1b4fc790c..edaad4f51c 100644 --- a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void GetString_EmptyBuffer_ReturnsEmptyString() { - var buffer = new ReadOnlySpan(); + var buffer = default(ReadOnlySpan); string result = Encoding.UTF8.GetString(buffer); diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 58317ca49f..b6bfca4b53 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -55,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Common { data[i] = data[i - 4] + 100f; } + return new Vector(data); } @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Common for (int i = 0; i < Vector.Count; i++) { - float v = (float)rnd.NextDouble() * (max - min) + min; + float v = ((float)rnd.NextDouble() * (max - min)) + min; data[i] = v; } @@ -104,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Common private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); return true; @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Common SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(normalized, dest); - byte[] expected = orig.Select(f => (byte)(f)).ToArray(); + byte[] expected = orig.Select(f => (byte)f).ToArray(); Assert.Equal(expected, dest); } @@ -177,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests.Common TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.BasicIntrinsics256.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -200,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); } private static void TestImpl_BulkConvertByteToNormalizedFloat( @@ -229,9 +230,9 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(ArraySizesDivisibleBy4))] public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] @@ -243,18 +244,16 @@ namespace SixLabors.ImageSharp.Tests.Common return; } - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.BasicIntrinsics256.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] [MemberData(nameof(ArraySizesDivisibleBy32))] public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] @@ -278,22 +277,38 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected2, actual2); } +#if SUPPORTS_RUNTIME_INTRINSICS + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void Avx2_BulkConvertNormalizedFloatToByteClampOverflows(int count) + { + if (!System.Runtime.Intrinsics.X86.Avx2.IsSupported) + { + return; + } + + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.Avx2Intrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); + } + +#endif + [Theory] [MemberData(nameof(ArbitraryArraySizes))] public void BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); - // for small values, let's stress test the implementation a bit: + // For small values, let's stress test the implementation a bit: if (count > 0 && count < 10) { for (int i = 0; i < 20; i++) { TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( count, - (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span), + (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span), i + 42); } } @@ -301,7 +316,9 @@ namespace SixLabors.ImageSharp.Tests.Common private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( int count, - Action, Memory> convert, int seed = -1) + Action, + Memory> convert, + int seed = -1) { seed = seed > 0 ? seed : count; float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); @@ -313,7 +330,7 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected, actual); } - private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, f * 255f + 0.5f)); + private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, (f * 255f) + 0.5f)); [Theory] [InlineData(0)] diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs index 8b2c65b07b..d47d5da8ef 100644 --- a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -67,7 +67,10 @@ namespace SixLabors.ImageSharp.Tests.Common public long Offset; public SeekOrigin Loc; - public SeekableStream(int capacity) : base(capacity) { } + public SeekableStream(int capacity) + : base(capacity) + { + } public override long Seek(long offset, SeekOrigin loc) { @@ -83,7 +86,10 @@ namespace SixLabors.ImageSharp.Tests.Common public List Counts = new List(); - public NonSeekableStream() : base(4) { } + public NonSeekableStream() + : base(4) + { + } public override int Read(byte[] buffer, int offset, int count) { @@ -97,7 +103,10 @@ namespace SixLabors.ImageSharp.Tests.Common { public override bool CanSeek => false; - public EofStream(int capacity) : base(capacity) { } + public EofStream(int capacity) + : base(capacity) + { + } public override int Read(byte[] buffer, int offset, int count) { diff --git a/tests/ImageSharp.Tests/Common/Tuple8.cs b/tests/ImageSharp.Tests/Common/Tuple8.cs index 3335e6e377..7c7f254db9 100644 --- a/tests/ImageSharp.Tests/Common/Tuple8.cs +++ b/tests/ImageSharp.Tests/Common/Tuple8.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Common.Tuples { @@ -95,4 +98,4 @@ namespace SixLabors.ImageSharp.Common.Tuples } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 6f68d04288..a68baf93fb 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// @@ -18,8 +18,11 @@ namespace SixLabors.ImageSharp.Tests public class ConfigurationTests { public Configuration ConfigurationEmpty { get; } + public Configuration DefaultConfiguration { get; } + private readonly int expectedDefaultConfigurationCount = 5; + public ConfigurationTests() { // the shallow copy of configuration should behave exactly like the default configuration, @@ -61,15 +64,30 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [InlineData(0)] - [InlineData(-42)] - public void Set_MaxDegreeOfParallelism_ToNonPositiveValue_Throws(int value) + [InlineData(-3, true)] + [InlineData(-2, true)] + [InlineData(-1, false)] + [InlineData(0, true)] + [InlineData(1, false)] + [InlineData(5, false)] + public void MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) { var cfg = new Configuration(); - Assert.Throws(() => cfg.MaxDegreeOfParallelism = value); + if (throws) + { + Assert.Throws( + () => + { + cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; + }); + } + else + { + cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; + Assert.Equal(maxDegreeOfParallelism, cfg.MaxDegreeOfParallelism); + } } - [Fact] public void ConstructorCallConfigureOnFormatProvider() { @@ -92,14 +110,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ConfigurationCannotAddDuplicates() { - const int count = 4; Configuration config = this.DefaultConfiguration; - Assert.Equal(count, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); - Assert.Equal(count, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); } [Fact] @@ -107,14 +124,14 @@ namespace SixLabors.ImageSharp.Tests { Configuration config = Configuration.CreateDefaultInstance(); - Assert.Equal(4, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); } [Fact] public void WorkingBufferSizeHint_DefaultIsCorrect() { Configuration config = this.DefaultConfiguration; - Assert.True(config.WorkingBufferSizeHintInBytes > 1024); + Assert.True(config.WorkingBufferSizeHintInBytes > 1024); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs b/tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs deleted file mode 100644 index de5b2bf474..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawBezierTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class DrawBezierTests - { - public static readonly TheoryData DrawPathData = new TheoryData - { - { "White", 255, 1.5f }, - { "Red", 255, 3 }, - { "HotPink", 255, 5 }, - { "HotPink", 150, 5 }, - { "White", 255, 15 }, - }; - - [Theory] - [WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] - public void DrawBeziers(TestImageProvider provider, string colorName, byte alpha, float thickness) - where TPixel : struct, IPixel - { - var points = new SixLabors.Primitives.PointF[] - { - new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) - }; - Rgba32 rgba = TestUtils.GetColorByName(colorName); - rgba.A = alpha; - Color color = rgba; - - FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; - - provider.RunValidatingProcessorTest( x => x.DrawBeziers(color, 5f, points), - testDetails, - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs deleted file mode 100644 index 76d29dff38..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawComplexPolygonTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Shapes; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class DrawComplexPolygonTests - { - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, true, false, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, true, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, true)] - public void DrawComplexPolygon(TestImageProvider provider, bool overlap, bool transparent, bool dashed) - where TPixel :struct, IPixel - { - var simplePath = new Polygon(new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300))); - - var hole1 = new Polygon(new LinearLineSegment( - new Vector2(37, 85), - overlap ? new Vector2(130, 40) : new Vector2(93, 85), - new Vector2(65, 137))); - IPath clipped = simplePath.Clip(hole1); - - Rgba32 colorRgba = Rgba32.White; - if (transparent) - { - colorRgba.A = 150; - } - - Color color = colorRgba; - - string testDetails = ""; - if (overlap) - { - testDetails += "_Overlap"; - } - - if (transparent) - { - testDetails += "_Transparent"; - } - - if (dashed) - { - testDetails += "_Dashed"; - } - - Pen pen = dashed ? Pens.Dash(color, 5f) : Pens.Solid(color, 5f); - - provider.RunValidatingProcessorTest( - x => x.Draw(pen, clipped), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs new file mode 100644 index 0000000000..0aff95d994 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.ImageSharp.Tests.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest + { + [Fact] + public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, 0.5f); + DrawImageProcessor dip = this.Verify(); + + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); + } + + [Fact] + public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f); + DrawImageProcessor dip = this.Verify(); + + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); + } + + [Fact] + public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, Point.Empty, 0.5f); + DrawImageProcessor dip = this.Verify(); + + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); + } + + [Fact] + public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + + this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f); + DrawImageProcessor dip = this.Verify(); + + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index 86c1c28504..d08d81899b 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)] public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image background = provider.GetImage()) using (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes)) @@ -45,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing appendSourceFileOrDescription: false); var comparer = ImageComparer.TolerantPercentage(0.01F); - background.CompareToReferenceOutput(comparer, + background.CompareToReferenceOutput( + comparer, provider, new { mode = mode }, appendPixelTypeToFileName: false, @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing string brushImage, PixelColorBlendingMode mode, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) @@ -89,7 +89,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing } image.DebugSave(provider, testInfo, encoder: encoder); - image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), provider, testInfo); } @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void DrawImageOfDifferentPixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes; @@ -127,7 +128,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image background = provider.GetImage()) using (var overlay = new Image(50, 50)) { - overlay.Mutate(c => c.Fill(Rgba32.Black)); + Assert.True(overlay.TryGetSinglePixelSpan(out Span overlaySpan)); + overlaySpan.Fill(Color.Black); background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); @@ -148,7 +150,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] public void DrawTransformed(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) @@ -167,7 +169,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing image.Mutate(x => x.DrawImage(blend, position, .75F)); image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.002f), + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.002f), provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); @@ -182,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y) { using (Image background = provider.GetImage()) - using (var overlay = new Image(Configuration.Default, 10, 10, Rgba32.Black)) + using (var overlay = new Image(Configuration.Default, 10, 10, Color.Black)) { ImageProcessingException ex = Assert.Throws(Test); @@ -190,11 +193,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing void Test() { - background.Mutate(context => context.DrawImage(overlay, new Point(x, y), GraphicsOptions.Default)); + background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions())); } } } - - } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs deleted file mode 100644 index 2836f8a38d..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class DrawLinesTests - { - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] - public void DrawLines_Simple(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : struct, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = new Pen(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] - public void DrawLines_Dash(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : struct, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.Dash(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)] - public void DrawLines_Dot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : struct, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.Dot(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)] - public void DrawLines_DashDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : struct, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.DashDot(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Black", 1f, 5, false)] - public void DrawLines_DashDotDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : struct, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.DashDotDot(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - - private static void DrawLinesImpl( - TestImageProvider provider, - string colorName, - float alpha, - float thickness, - bool antialias, - Pen pen) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - - GraphicsOptions options = new GraphicsOptions(antialias); - - string aa = antialias ? "" : "_NoAntialias"; - FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; - - provider.RunValidatingProcessorTest( - c => c.DrawLines(options, pen, simplePath), - outputDetails, - appendSourceFileOrDescription: false); - } - - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs deleted file mode 100644 index 8c2c6fc6ed..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class DrawPathTests - { - public static readonly TheoryData DrawPathData = new TheoryData - { - { "White", 255, 1.5f }, - { "Red", 255, 3 }, - { "HotPink", 255, 5 }, - { "HotPink", 150, 5 }, - { "White", 255, 15 }, - }; - - [Theory] - [WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] - public void DrawPath(TestImageProvider provider, string colorName, byte alpha, float thickness) - where TPixel : struct, IPixel - { - var linearSegment = new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300)); - var bezierSegment = new CubicBezierLineSegment( - new Vector2(50, 300), - new Vector2(500, 500), - new Vector2(60, 10), - new Vector2(10, 400)); - - var path = new Path(linearSegment, bezierSegment); - - Rgba32 rgba = TestUtils.GetColorByName(colorName); - rgba.A = alpha; - Color color = rgba; - - FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; - - provider.RunValidatingProcessorTest( - x => x.Draw(color, thickness, path), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(256, 256, "Black", PixelTypes.Rgba32)] - public void PathExtendingOffEdgeOfImageShouldNotBeCropped(TestImageProvider provider) - where TPixel : struct, IPixel - { - var color = Color.White; - Pen pen = Pens.Solid(color, 5f); - - provider.RunValidatingProcessorTest( - x => - { - for (int i = 0; i < 300; i += 20) - { - var points = new PointF[] { new Vector2(100, 2), new Vector2(-10, i) }; - x.DrawLines(pen, points); - } - }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs deleted file mode 100644 index 18fde6ad8f..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class DrawPolygonTests - { - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] - public void DrawPolygon(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = - { - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }; - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - - GraphicsOptions options = new GraphicsOptions(antialias); - - string aa = antialias ? "" : "_NoAntialias"; - FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; - - provider.RunValidatingProcessorTest( - c => c.DrawPolygon(options, color, thickness, simplePath), - outputDetails, - appendSourceFileOrDescription: false); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs deleted file mode 100644 index e0fff8da50..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillComplexPolygonTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Shapes; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class FillComplexPolygonTests - { - [Theory] - [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, false)] - [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, true, false)] - [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, true)] - public void ComplexPolygon_SolidFill(TestImageProvider provider, bool overlap, bool transparent) - where TPixel :struct, IPixel - { - var simplePath = new Polygon(new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300))); - - var hole1 = new Polygon(new LinearLineSegment( - new Vector2(37, 85), - overlap ? new Vector2(130, 40) : new Vector2(93, 85), - new Vector2(65, 137))); - IPath clipped = simplePath.Clip(hole1); - - Rgba32 colorRgba = Rgba32.HotPink; - if (transparent) - { - colorRgba.A = 150; - } - - Color color = colorRgba; - - string testDetails = ""; - if (overlap) - { - testDetails += "_Overlap"; - } - - if (transparent) - { - testDetails += "_Transparent"; - } - - provider.RunValidatingProcessorTest( - x => x.Fill(color, clipped), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs deleted file mode 100644 index c61f770c9f..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - - [GroupOutput("Drawing/GradientBrushes")] - public class FillEllipticGradientBrushTests - { - public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage( - TestImageProvider provider) - where TPixel : struct, IPixel - { - Color red = Color.Red; - - using (Image image = provider.GetImage()) - { - var unicolorLinearGradientBrush = - new EllipticGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(10, 0), - 1.0f, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - // no need for reference image in this test: - image.ComparePixelBufferTo(red); - } - } - - [Theory] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)] - public void AxisParallelEllipsesWithDifferentRatio( - TestImageProvider provider, - float ratio) - where TPixel : struct, IPixel - { - Color yellow = Color.Yellow; - Color red = Color.Red; - Color black = Color.Black; - - provider.VerifyOperation( - TolerantComparer, - image => - { - var unicolorLinearGradientBrush = new EllipticGradientBrush( - new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), - new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3), - ratio, - GradientRepetitionMode.None, - new ColorStop(0, yellow), - new ColorStop(1, red), - new ColorStop(1, black)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - $"{ratio:F2}", - false, - false); - } - - [Theory] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 0)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 0)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 0)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 0)] - - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 45)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 45)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 45)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 45)] - - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 90)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 90)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 90)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 90)] - - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 30)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)] - public void RotatedEllipsesWithDifferentRatio( - TestImageProvider provider, - float ratio, - float rotationInDegree) - where TPixel: struct, IPixel - { - FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; - - provider.VerifyOperation( - TolerantComparer, - image => - { - Color yellow = Color.Yellow; - Color red = Color.Red; - Color black = Color.Black; - - var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); - - double rotation = (Math.PI * rotationInDegree) / 180.0; - double cos = Math.Cos(rotation); - double sin = Math.Sin(rotation); - - int offsetY = image.Height / 6; - int axisX = center.X + (int)-(offsetY * sin); - int axisY = center.Y + (int)(offsetY * cos); - - var unicolorLinearGradientBrush = new EllipticGradientBrush( - center, - new SixLabors.Primitives.Point(axisX, axisY), - ratio, - GradientRepetitionMode.None, - new ColorStop(0, yellow), - new ColorStop(1, red), - new ColorStop(1, black)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - variant, - false, - false); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs deleted file mode 100644 index cbf49b8301..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; - -using Xunit; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class FillImageBrushTests - { - [Fact] - public void DoesNotDisposeImage() - { - using (var src = new Image(5, 5)) - { - var brush = new ImageBrush(src); - using (var dest = new Image(10, 10)) - { - dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); - dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); - } - } - } - - [Theory] - [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void UseBrushOfDifferentPixelType(TestImageProvider provider) - where TPixel : struct, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using (Image background = provider.GetImage()) - using (Image overlay = provider.PixelType == PixelTypes.Rgba32 - ? (Image)Image.Load(data) - : Image.Load(data)) - { - var brush = new ImageBrush(overlay); - background.Mutate(c => c.Fill(brush)); - - background.DebugSave(provider, appendSourceFileOrDescription : false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs deleted file mode 100644 index 361e7e70d1..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Globalization; -using System.Linq; -using System.Text; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using Xunit; -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - using SixLabors.ImageSharp.Advanced; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - - [GroupOutput("Drawing/GradientBrushes")] - public class FillLinearGradientBrushTests - { - public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - Color red = Color.Red; - - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(10, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - // no need for reference image in this test: - image.ComparePixelBufferTo(red); - } - } - - [Theory] - [WithBlankImages(20, 10, PixelTypes.Rgba32)] - [WithBlankImages(20, 10, PixelTypes.Argb32)] - [WithBlankImages(20, 10, PixelTypes.Rgb24)] - public void DoesNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(image.Width, 0), - GradientRepetitionMode.None, - new ColorStop(0, Color.Blue), - new ColorStop(1, Color.Yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBlankImages(500, 10, PixelTypes.Rgba32)] - public void HorizontalReturnsUnicolorColumns(TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(image.Width, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - false, - false); - } - - [Theory] - [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)] - [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)] - [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)] - [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)] - public void HorizontalGradientWithRepMode( - TestImageProvider provider, - GradientRepetitionMode repetitionMode) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(image.Width / 10, 0), - repetitionMode, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - $"{repetitionMode}", - false, - false); - } - - [Theory] - [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.5f })] - [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })] - [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })] - public void WithDoubledStopsProduceDashedPatterns( - TestImageProvider provider, - float[] pattern) - where TPixel : struct, IPixel - { - string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); - - // ensure the input data is valid - Assert.True(pattern.Length > 0); - - Color black = Color.Black; - Color white = Color.White; - - // create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white. - ColorStop[] colorStops = - Enumerable.Repeat(new ColorStop(0, black), 1) - .Concat( - pattern - .SelectMany((f, index) => new[] - { - new ColorStop(f, index % 2 == 0 ? black : white), - new ColorStop(f, index % 2 == 0 ? white : black) - })) - .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) - .ToArray(); - - using (Image image = provider.GetImage()) - { - var unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(image.Width, 0), - GradientRepetitionMode.None, - colorStops); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - image.DebugSave( - provider, - variant, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - // the result must be a black and white pattern, no other color should occur: - Assert.All( - Enumerable.Range(0, image.Width).Select(i => image[i, 0]), - color => Assert.True( - color.Equals(black.ToPixel()) || color.Equals(white.ToPixel()))); - - image.CompareToReferenceOutput( - TolerantComparer, - provider, - variant, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBlankImages(10, 500, PixelTypes.Rgba32)] - public void VerticalBrushReturnsUnicolorRows( - TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(0, image.Height), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - VerifyAllRowsAreUnicolor(image); - }, - false, - false); - - void VerifyAllRowsAreUnicolor(Image image) - { - for (int y = 0; y < image.Height; y++) - { - Span row = image.GetPixelRowSpan(y); - TPixel firstColorOfRow = row[0]; - foreach (TPixel p in row) - { - Assert.Equal(firstColorOfRow, p); - } - } - } - } - - public enum ImageCorner - { - TopLeft = 0, - TopRight = 1, - BottomLeft = 2, - BottomRight = 3 - } - - [Theory] - [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)] - public void DiagonalReturnsCorrectImages( - TestImageProvider provider, - ImageCorner startCorner) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not."); - - int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1; - int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1; - int endX = image.Height - startX - 1; - int endY = image.Width - startY - 1; - - Color red = Color.Red; - Color yellow = Color.Yellow; - - var unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(startX, startY), - new SixLabors.Primitives.Point(endX, endY), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave( - provider, - startCorner, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - int verticalSign = startY == 0 ? 1 : -1; - int horizontalSign = startX == 0 ? 1 : -1; - - for (int i = 0; i < image.Height; i++) - { - // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) - TPixel colorOnDiagonal = image[i, i]; - - // TODO: This is incorrect. from -0 to < 0 ?? - int orthoCount = 0; - for (int offset = -orthoCount; offset < orthoCount; offset++) - { - Assert.Equal(colorOnDiagonal, image[i + (horizontalSign * offset), i + (verticalSign * offset)]); - } - } - - image.CompareToReferenceOutput( - TolerantComparer, - provider, - startCorner, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f }, new[] { 0, 1, 2, 0 })] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f }, new[] { 0, 1, 3 })] - public void ArbitraryGradients( - TestImageProvider provider, - int startX, int startY, - int endX, int endY, - float[] stopPositions, - int[] stopColorCodes) - where TPixel : struct, IPixel - { - Color[] colors = - { - Color.Navy, Color.LightGreen, Color.Yellow, - Color.Red - }; - - var coloringVariant = new StringBuilder(); - var colorStops = new ColorStop[stopPositions.Length]; - - for (int i = 0; i < stopPositions.Length; i++) - { - Color color = colors[stopColorCodes[i % colors.Length]]; - float position = stopPositions[i]; - colorStops[i] = new ColorStop(position, color); - Rgba32 rgba = color; - coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); - } - - FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; - - provider.VerifyOperation( - image => - { - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(startX, startY), - new SixLabors.Primitives.Point(endX, endY), - GradientRepetitionMode.None, - colorStops); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - variant, - false, - false); - } - - [Theory] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })] - public void MultiplePointGradients( - TestImageProvider provider, - int startX, int startY, - int endX, int endY, - float[] stopPositions, - int[] stopColorCodes) - where TPixel : struct, IPixel - { - Color[] colors = - { - Color.Black, Color.Blue, Color.Red, - Color.White, Color.Lime - }; - - var coloringVariant = new StringBuilder(); - var colorStops = new ColorStop[stopPositions.Length]; - - for (int i = 0; i < stopPositions.Length; i++) - { - Color color = colors[stopColorCodes[i % colors.Length]]; - float position = stopPositions[i]; - colorStops[i] = new ColorStop(position, color); - Rgba32 rgba = color; - coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); - } - - FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; - - provider.VerifyOperation( - image => - { - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(startX, startY), - new SixLabors.Primitives.Point(endX, endY), - GradientRepetitionMode.None, - colorStops); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - variant, - false, - false); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs deleted file mode 100644 index 1ab747bafc..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing/GradientBrushes")] - public class FillPathGradientBrushTests - { - public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void FillRectangleWithDifferentColors(TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; - Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; - - var brush = new PathGradientBrush(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - } - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void FillTriangleWithDifferentColors(TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = { new PointF(5, 0), new PointF(10, 10), new PointF(0, 10) }; - Color[] colors = { Color.Red, Color.Green, Color.Blue }; - - var brush = new PathGradientBrush(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - } - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void FillRectangleWithSingleColor(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; - Color[] colors = { Color.Red }; - - var brush = new PathGradientBrush(points, colors); - - image.Mutate(x => x.Fill(brush)); - - image.ComparePixelBufferTo(Color.Red); - } - } - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void ShouldRotateTheColorsWhenThereAreMorePoints(TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; - Color[] colors = { Color.Red, Color.Yellow }; - - var brush = new PathGradientBrush(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - } - - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void FillWithCustomCenterColor(TestImageProvider provider) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; - Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; - - var brush = new PathGradientBrush(points, colors, Color.White); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - } - - [Fact] - public void ShouldThrowArgumentNullExceptionWhenLinesAreNull() - { - Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; - - PathGradientBrush Create() => new PathGradientBrush(null, colors, Color.White); - - Assert.Throws(Create); - } - - [Fact] - public void ShouldThrowArgumentOutOfRangeExceptionWhenLessThan3PointsAreGiven() - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0) }; - Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; - - PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White); - - Assert.Throws(Create); - } - - [Fact] - public void ShouldThrowArgumentNullExceptionWhenColorsAreNull() - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; - - PathGradientBrush Create() => new PathGradientBrush(points, null, Color.White); - - Assert.Throws(Create); - } - - [Fact] - public void ShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven() - { - PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; - - var colors = new Color[0]; - - PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White); - - Assert.Throws(Create); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs deleted file mode 100644 index 647f285103..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillPatternBrushTests.cs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - public class FillPatternBrushTests - { - private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern) - { - string path = TestEnvironment.CreateOutputDirectory("Drawing", "FillPatternBrushTests"); - using (var image = new Image(20, 20)) - { - image.Mutate(x => x.Fill(background).Fill(brush)); - - image.Save($"{path}/{name}.png"); - - Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); - // lets pick random spots to start checking - var r = new Random(); - var expectedPatternFast = new DenseMatrix(expectedPattern); - int xStride = expectedPatternFast.Columns; - int yStride = expectedPatternFast.Rows; - int offsetX = r.Next(image.Width / xStride) * xStride; - int offsetY = r.Next(image.Height / yStride) * yStride; - for (int x = 0; x < xStride; x++) - { - for (int y = 0; y < yStride; y++) - { - int actualX = x + offsetX; - int actualY = y + offsetY; - Rgba32 expected = expectedPatternFast[y, x]; // inverted pattern - Rgba32 actual = sourcePixels[actualX, actualY]; - if (expected != actual) - { - Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); - } - } - } - - image.Mutate(x => x.Resize(80, 80, KnownResamplers.NearestNeighbor)); - image.Save($"{path}/{name}x4.png"); - } - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent10() - { - this.Test( - "Percent10", - Rgba32.Blue, - Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen), - new[,] - { - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent10Transparent() - { - this.Test( - "Percent10_Transparent", - Rgba32.Blue, - Brushes.Percent10(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent20() - { - this.Test( - "Percent20", - Rgba32.Blue, - Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] - { - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent20_transparent() - { - this.Test( - "Percent20_Transparent", - Rgba32.Blue, - Brushes.Percent20(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithHorizontal() - { - this.Test( - "Horizontal", - Rgba32.Blue, - Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] - { - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithHorizontal_transparent() - { - this.Test( - "Horizontal_Transparent", - Rgba32.Blue, - Brushes.Horizontal(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithMin() - { - this.Test( - "Min", - Rgba32.Blue, - Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] - { - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithMin_transparent() - { - this.Test( - "Min_Transparent", - Rgba32.Blue, - Brushes.Min(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithVertical() - { - this.Test( - "Vertical", - Rgba32.Blue, - Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] - { - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithVertical_transparent() - { - this.Test( - "Vertical_Transparent", - Rgba32.Blue, - Brushes.Vertical(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithForwardDiagonal() - { - this.Test( - "ForwardDiagonal", - Rgba32.Blue, - Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] - { - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent() - { - this.Test( - "ForwardDiagonal_Transparent", - Rgba32.Blue, - Brushes.ForwardDiagonal(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithBackwardDiagonal() - { - this.Test( - "BackwardDiagonal", - Rgba32.Blue, - Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] - { - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink } - }); - } - - [Fact] - public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent() - { - this.Test( - "BackwardDiagonal_Transparent", - Rgba32.Blue, - Brushes.BackwardDiagonal(Rgba32.HotPink), - new Rgba32[,] - { - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink } - }); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs deleted file mode 100644 index 104237ec3e..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Shapes; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class FillPolygonTests - { - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)] - public void FillPolygon_Solid(TestImageProvider provider, string colorName, float alpha, bool antialias) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = - { - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }; - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - - var options = new GraphicsOptions(antialias); - - string aa = antialias ? "" : "_NoAntialias"; - FormattableString outputDetails = $"{colorName}_A{alpha}{aa}"; - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(options, color, simplePath), - outputDetails, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)] - public void FillPolygon_Concave(TestImageProvider provider) - where TPixel : struct, IPixel - { - var points = new SixLabors.Primitives.PointF[] - { - new Vector2(8, 8), - new Vector2(64, 8), - new Vector2(64, 64), - new Vector2(120, 64), - new Vector2(120, 120), - new Vector2(8, 120) - }; - - var color = Color.LightGreen; - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(color, points), - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] - public void FillPolygon_Pattern(TestImageProvider provider) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = - { - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }; - var color = Color.Yellow; - - var brush = Brushes.Horizontal(color); - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(brush, simplePath), - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] - public void FillPolygon_ImageBrush(TestImageProvider provider, string brushImageName) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = - { - new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) - }; - - using (Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes)) - { - var brush = new ImageBrush(brushImage); - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(brush, simplePath), - System.IO.Path.GetFileNameWithoutExtension(brushImageName), - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)] - public void Fill_RectangularPolygon(TestImageProvider provider) - where TPixel : struct, IPixel - { - var polygon = new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140); - var color = Color.White; - - provider.RunValidatingProcessorTest( - c => c.Fill(color, polygon), - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)] - public void Fill_RegularPolygon(TestImageProvider provider, int vertices, float radius, float angleDeg) - where TPixel : struct, IPixel - { - float angle = GeometryUtilities.DegreeToRadian(angleDeg); - var polygon = new RegularPolygon(100, 100, vertices, radius, angle); - var color = Color.Yellow; - - FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})"; - provider.RunValidatingProcessorTest( - c => c.Fill(color, polygon), - testOutput, - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)] - public void Fill_EllipsePolygon(TestImageProvider provider) - where TPixel : struct, IPixel - { - var polygon = new EllipsePolygon(100, 100, 80, 120); - var color = Color.Azure; - - provider.RunValidatingProcessorTest( - c => c.Fill(color, polygon), - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs deleted file mode 100644 index 818340dd22..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - - [GroupOutput("Drawing/GradientBrushes")] - public class FillRadialGradientBrushTests - { - public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImages(200, 200, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage( - TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - Color red = Color.Red; - - var unicolorRadialGradientBrush = - new RadialGradientBrush( - new SixLabors.Primitives.Point(0, 0), - 100, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - // no need for reference image in this test: - image.ComparePixelBufferTo(red); - } - } - - [Theory] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 100)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)] - [WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)] - public void WithDifferentCentersReturnsImage( - TestImageProvider provider, - int centerX, - int centerY) - where TPixel : struct, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - var brush = new RadialGradientBrush( - new SixLabors.Primitives.Point(centerX, centerY), - image.Width / 2f, - GradientRepetitionMode.None, - new ColorStop(0, Color.Red), - new ColorStop(1, Color.Yellow)); - - image.Mutate(x => x.Fill(brush)); - }, - $"center({centerX},{centerY})", - false, - false); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs deleted file mode 100644 index c0388ea2d4..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -using Moq; -using System; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; -using Xunit; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.Shapes; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - - - public class FillRegionProcessorTests - { - - [Theory] - [InlineData(true, 1, 4)] - [InlineData(true, 2, 4)] - [InlineData(true, 5, 5)] - [InlineData(true, 8, 8)] - [InlineData(false, 8, 4)] - [InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialiasing is off. - public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) - { - var bounds = new Rectangle(0, 0, 1, 1); - - var brush = new Mock(); - var region = new MockRegion2(bounds); - - var options = new GraphicsOptions(antialias) - { - AntialiasSubpixelDepth = 1 - }; - var processor = new FillRegionProcessor(brush.Object, region, options); - var img = new Image(1, 1); - processor.Execute(img, bounds); - - Assert.Equal(4, region.ScanInvocationCounter); - } - - [Fact] - public void FillOffCanvas() - { - var bounds = new Rectangle(-100, -10, 10, 10); - var brush = new Mock(); - var options = new GraphicsOptions(true); - var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options); - var img = new Image(10, 10); - processor.Execute(img, bounds); - } - - [Fact] - public void DrawOffCanvas() - { - - using (var img = new Image(10, 10)) - { - img.Mutate(x => x.DrawLines(new Pen(Rgba32.Black, 10), - new Vector2(-10, 5), - new Vector2(20, 5))); - } - } - - [Fact] - public void DoesNotThrowForIssue928() - { - var rectText = new RectangleF(0, 0, 2000, 2000); - using (Image img = new Image((int)rectText.Width, (int)rectText.Height)) - { - img.Mutate(x => x.Fill(Rgba32.Transparent)); - - img.Mutate(ctx => { - ctx.DrawLines( - Rgba32.Red, - 0.984252f, - new PointF(104.762581f, 1074.99365f), - new PointF(104.758667f, 1075.01721f), - new PointF(104.757675f, 1075.04114f), - new PointF(104.759628f, 1075.065f), - new PointF(104.764488f, 1075.08838f), - new PointF(104.772186f, 1075.111f), - new PointF(104.782608f, 1075.13245f), - new PointF(104.782608f, 1075.13245f) - ); - } - ); - } - } - - [Fact] - public void DoesNotThrowFillingTriangle() - { - using(var image = new Image(28, 28)) - { - var path = new Polygon( - new LinearLineSegment(new PointF(17.11f, 13.99659f), new PointF(14.01433f, 27.06201f)), - new LinearLineSegment(new PointF(14.01433f, 27.06201f), new PointF(13.79267f, 14.00023f)), - new LinearLineSegment(new PointF(13.79267f, 14.00023f), new PointF(17.11f, 13.99659f)) - ); - - image.Mutate(ctx => - { - ctx.Fill(Rgba32.White, path); - }); - } - } - - // Mocking the region throws an error in netcore2.0 - private class MockRegion1 : Region - { - public override Rectangle Bounds => new Rectangle(-100, -10, 10, 10); - - public override int Scan(float y, Span buffer, Configuration configuration) - { - if (y < 5) - { - buffer[0] = -10f; - buffer[1] = 100f; - return 2; - } - return 0; - } - - public override int MaxIntersections => 10; - } - - private class MockRegion2 : Region - { - public MockRegion2(Rectangle bounds) - { - this.Bounds = bounds; - } - - public override int MaxIntersections => 100; - - public override Rectangle Bounds { get; } - - public int ScanInvocationCounter { get; private set; } - - public override int Scan(float y, Span buffer, Configuration configuration) - { - this.ScanInvocationCounter++; - return 0; - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs deleted file mode 100644 index a5e7450839..0000000000 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; -using SixLabors.Shapes; - -using Xunit; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class FillSolidBrushTests - { - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - [WithBlankImages(7, 4, PixelTypes.Rgba32)] - [WithBlankImages(16, 7, PixelTypes.Rgba32)] - [WithBlankImages(33, 32, PixelTypes.Rgba32)] - [WithBlankImages(400, 500, PixelTypes.Rgba32)] - public void DoesNotDependOnSize(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - var color = Color.HotPink; - image.Mutate(c => c.Fill(color)); - - image.DebugSave(provider, appendPixelTypeToFileName: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] - public void DoesNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - var color = Color.HotPink; - image.Mutate(c => c.Fill(color)); - - image.DebugSave(provider, appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] - [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] - public void WhenColorIsOpaque_OverridePreviousColor( - TestImageProvider provider, - string newColorName) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = TestUtils.GetColorByName(newColorName); - image.Mutate(c => c.Fill(color)); - - image.DebugSave( - provider, - newColorName, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion(TestImageProvider provider, int x0, int y0, int w, int h) - where TPixel : struct, IPixel - { - FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; - var region = new RectangleF(x0, y0, w, h); - Color color = TestUtils.GetColorByName("Blue"); - - provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact); - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion_WorksOnWrappedMemoryImage( - TestImageProvider provider, - int x0, - int y0, - int w, - int h) - where TPixel : struct, IPixel - { - FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; - var region = new RectangleF(x0, y0, w, h); - Color color = TestUtils.GetColorByName("Blue"); - - provider.RunValidatingProcessorTestOnWrappedMemoryImage( - c => c.Fill(color, region), - testDetails, - ImageComparer.Exact, - useReferenceOutputFrom: nameof(this.FillRegion)); - } - - public static readonly TheoryData BlendData = - new TheoryData - { - { false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - }; - - [Theory] - [WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)] - public void BlendFillColorOverBackground( - TestImageProvider provider, - bool triggerFillRegion, - string newColorName, - float alpha, - PixelColorBlendingMode blenderMode, - float blendPercentage) - where TPixel : struct, IPixel - { - Color fillColor = TestUtils.GetColorByName(newColorName).WithAlpha(alpha); - - using (Image image = provider.GetImage()) - { - TPixel bgColor = image[0, 0]; - - var options = new GraphicsOptions(false) - { - ColorBlendingMode = blenderMode, BlendPercentage = blendPercentage - }; - - if (triggerFillRegion) - { - var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16)); - - image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region)); - } - else - { - image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); - } - - var testOutputDetails = new - { - triggerFillRegion = triggerFillRegion, - newColorName = newColorName, - alpha = alpha, - blenderMode = blenderMode, - blendPercentage = blendPercentage - }; - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - PixelBlender blender = PixelOperations.Instance.GetPixelBlender( - blenderMode, - PixelAlphaCompositionMode.SrcOver); - TPixel expectedPixel = blender.Blend(bgColor, fillColor.ToPixel(), blendPercentage); - - image.ComparePixelBufferTo(expectedPixel); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs deleted file mode 100644 index 3691b54ce3..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Tests.Processing; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class DrawPathCollection : BaseImageOperationsExtensionTest - { - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - Pen pen = Pens.Solid(Rgba32.HotPink, 1); - IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - - IPathCollection pathCollection; - - public DrawPathCollection() - { - this.pathCollection = new PathCollection(this.path1, this.path2); - } - - [Fact] - public void CorrectlySetsBrushAndPath() - { - this.operations.Draw(this.pen, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath region = Assert.IsType(processor.Region); - - // path is converted to a polygon before filling - Assert.IsType(region.Shape); - - Assert.Equal(this.pen.StrokeFill, processor.Brush); - } - } - - [Fact] - public void CorrectlySetsBrushPathOptions() - { - this.operations.Draw(this.noneDefault, this.pen, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapePath region = Assert.IsType(processor.Region); - Assert.IsType(region.Shape); - - Assert.Equal(this.pen.StrokeFill, processor.Brush); - } - } - - [Fact] - public void CorrectlySetsColorAndPath() - { - this.operations.Draw(this.color, 1, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath region = Assert.IsType(processor.Region); - Assert.IsType(region.Shape); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } - - [Fact] - public void CorrectlySetsColorPathAndOptions() - { - this.operations.Draw(this.noneDefault, this.color, 1, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapePath region = Assert.IsType(processor.Region); - Assert.IsType(region.Shape); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs deleted file mode 100644 index 160ff22a3e..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Tests.Processing; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class FillPath : BaseImageOperationsExtensionTest - { - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - IPath path = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - - [Fact] - public void CorrectlySetsBrushAndPath() - { - this.operations.Fill(this.brush, this.path); - var processor = this.Verify(); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - - // path is converted to a polygon before filling - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsBrushPathOptions() - { - this.operations.Fill(this.noneDefault, this.brush, this.path); - var processor = this.Verify(); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsColorAndPath() - { - this.operations.Fill(this.color, this.path); - var processor = this.Verify(); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorPathAndOptions() - { - this.operations.Fill(this.noneDefault, this.color, this.path); - var processor = this.Verify(); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs deleted file mode 100644 index b76ee8ffcd..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Tests.Processing; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class FillPathCollection : BaseImageOperationsExtensionTest - { - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - - IPathCollection pathCollection; - - public FillPathCollection() - { - this.pathCollection = new PathCollection(this.path1, this.path2); - } - - [Fact] - public void CorrectlySetsBrushAndPath() - { - this.operations.Fill(this.brush, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - - // path is converted to a polygon before filling - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - Assert.Equal(this.brush, processor.Brush); - } - } - - [Fact] - public void CorrectlySetsBrushPathOptions() - { - this.operations.Fill(this.noneDefault, this.brush, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - Assert.Equal(this.brush, processor.Brush); - } - } - - [Fact] - public void CorrectlySetsColorAndPath() - { - this.operations.Fill(this.color, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } - - [Fact] - public void CorrectlySetsColorPathAndOptions() - { - this.operations.Fill(this.noneDefault, this.color, this.pathCollection); - - for (int i = 0; i < 2; i++) - { - FillRegionProcessor processor = this.Verify(i); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs deleted file mode 100644 index c62a871481..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Tests.Processing; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class FillPolygon : BaseImageOperationsExtensionTest - { - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - SixLabors.Primitives.PointF[] path = { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - }; - - - [Fact] - public void CorrectlySetsBrushAndPath() - { - this.operations.FillPolygon(this.brush, this.path); - - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsBrushPathAndOptions() - { - this.operations.FillPolygon(this.noneDefault, this.brush, this.path); - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsColorAndPath() - { - this.operations.FillPolygon(this.color, this.path); - FillRegionProcessor processor = this.Verify(); - - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorPathAndOptions() - { - this.operations.FillPolygon(this.noneDefault, this.color, this.path); - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Polygon polygon = Assert.IsType(region.Shape); - Assert.IsType(polygon.LineSegments[0]); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs deleted file mode 100644 index 17a2b87c0e..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Tests.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class FillRectangle : BaseImageOperationsExtensionTest - { - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76); - - [Fact] - public void CorrectlySetsBrushAndRectangle() - { - this.operations.Fill(this.brush, this.rectangle); - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); - Assert.Equal(rect.Location.X, this.rectangle.X); - Assert.Equal(rect.Location.Y, this.rectangle.Y); - Assert.Equal(rect.Size.Width, this.rectangle.Width); - Assert.Equal(rect.Size.Height, this.rectangle.Height); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsBrushRectangleAndOptions() - { - this.operations.Fill(this.noneDefault, this.brush, this.rectangle); - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); - Assert.Equal(rect.Location.X, this.rectangle.X); - Assert.Equal(rect.Location.Y, this.rectangle.Y); - Assert.Equal(rect.Size.Width, this.rectangle.Width); - Assert.Equal(rect.Size.Height, this.rectangle.Height); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsColorAndRectangle() - { - this.operations.Fill(this.color, this.rectangle); - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); - Assert.Equal(rect.Location.X, this.rectangle.X); - Assert.Equal(rect.Location.Y, this.rectangle.Y); - Assert.Equal(rect.Size.Width, this.rectangle.Width); - Assert.Equal(rect.Size.Height, this.rectangle.Height); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorRectangleAndOptions() - { - this.operations.Fill(this.noneDefault, this.color, this.rectangle); - FillRegionProcessor processor = this.Verify(); - - Assert.Equal(this.noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); - Assert.Equal(rect.Location.X, this.rectangle.X); - Assert.Equal(rect.Location.Y, this.rectangle.Y); - Assert.Equal(rect.Size.Width, this.rectangle.Width); - Assert.Equal(rect.Size.Height, this.rectangle.Height); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(this.color, brush.Color); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs deleted file mode 100644 index b474f6e47c..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class ShapePathTests - { - // TODO read these back in - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs deleted file mode 100644 index 69dff72369..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Primitives; -using System; -using System.Collections.Generic; -using System.Numerics; -using Moq; -using SixLabors.Primitives; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Paths -{ - public class ShapeRegionTests - { - public abstract class MockPath : IPath - { - public abstract RectangleF Bounds { get; } - public IPath AsClosedPath() => this; - - public abstract SegmentInfo PointAlongPath(float distanceAlongPath); - public abstract PointInfo Distance(PointF point); - public abstract IEnumerable Flatten(); - public abstract bool Contains(PointF point); - public abstract IPath Transform(Matrix3x2 matrix); - public abstract PathTypes PathType { get; } - public abstract int MaxIntersections { get; } - public abstract float Length { get; } - - public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset) - { - return this.FindIntersections(start, end, buffer, 0); - } - - public int FindIntersections(PointF s, PointF e, Span buffer) - { - Assert.Equal(this.TestYToScan, s.Y); - Assert.Equal(this.TestYToScan, e.Y); - Assert.True(s.X < this.Bounds.Left); - Assert.True(e.X > this.Bounds.Right); - - this.TestFindIntersectionsInvocationCounter++; - - return this.TestFindIntersectionsResult; - } - - public int TestFindIntersectionsInvocationCounter { get; private set; } - public virtual int TestYToScan => 10; - public virtual int TestFindIntersectionsResult => 3; - } - - private readonly Mock pathMock; - - private readonly RectangleF bounds; - - public ShapeRegionTests() - { - this.pathMock = new Mock { CallBase = true }; - - this.bounds = new RectangleF(10.5f, 10, 10, 10); - this.pathMock.Setup(x => x.Bounds).Returns(this.bounds); - } - - [Fact] - public void ShapeRegionWithPathRetainsShape() - { - var region = new ShapeRegion(this.pathMock.Object); - - Assert.Equal(this.pathMock.Object, region.Shape); - } - - [Fact] - public void ShapeRegionFromPathConvertsBoundsProxyToShape() - { - var region = new ShapeRegion(this.pathMock.Object); - - Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); - - this.pathMock.Verify(x => x.Bounds); - } - - [Fact] - public void ShapeRegionFromPathMaxIntersectionsProxyToShape() - { - var region = new ShapeRegion(this.pathMock.Object); - - int i = region.MaxIntersections; - this.pathMock.Verify(x => x.MaxIntersections); - } - - [Fact] - public void ShapeRegionFromPathScanYProxyToShape() - { - MockPath path = this.pathMock.Object; - int yToScan = path.TestYToScan; - var region = new ShapeRegion(path); - - int i = region.Scan(yToScan, new float[path.TestFindIntersectionsResult], Configuration.Default); - - Assert.Equal(path.TestFindIntersectionsResult, i); - Assert.Equal(1, path.TestFindIntersectionsInvocationCounter); - } - - - [Fact] - public void ShapeRegionFromShapeConvertsBoundsProxyToShape() - { - var region = new ShapeRegion(this.pathMock.Object); - - Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); - - this.pathMock.Verify(x => x.Bounds); - } - - [Fact] - public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() - { - var region = new ShapeRegion(this.pathMock.Object); - - int i = region.MaxIntersections; - this.pathMock.Verify(x => x.MaxIntersections); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs deleted file mode 100644 index fae0bf72b4..0000000000 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class RecolorImageTests - { - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, "Yellow", "Pink", 0.2f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.6f)] - public void Recolor(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) - where TPixel : struct, IPixel - { - Color sourceColor = TestUtils.GetColorByName(sourceColorName); - Color targetColor = TestUtils.GetColorByName(targetColorName); - var brush = new RecolorBrush(sourceColor, targetColor, threshold); - - FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; - provider.RunValidatingProcessorTest(x => x.Fill(brush), testInfo); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] - public void Recolor_InBox(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) - where TPixel : struct, IPixel - { - Color sourceColor = TestUtils.GetColorByName(sourceColorName); - Color targetColor = TestUtils.GetColorByName(targetColorName); - var brush = new RecolorBrush(sourceColor, targetColor, threshold); - - FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; - provider.RunValidatingProcessorTest(x => - { - Size size = x.GetCurrentSize(); - var rectangle = new Rectangle(0, size.Height / 2 - size.Height / 4, size.Width, size.Height / 2); - x.Fill(brush, rectangle); - }, testInfo); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs deleted file mode 100644 index fd8713cccd..0000000000 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Shapes; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class SolidBezierTests - { - [Theory] - [WithBlankImages(500, 500, PixelTypes.Rgba32)] - public void FilledBezier(TestImageProvider provider) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = { - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - }; - - Color blue = Color.Blue; - Color hotPink = Color.HotPink; - - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BackgroundColor(blue)); - image.Mutate(x => x.Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } - } - - - [Theory] - [WithBlankImages(500, 500, PixelTypes.Rgba32)] - public void OverlayByFilledPolygonOpacity(TestImageProvider provider) - where TPixel : struct, IPixel - { - SixLabors.Primitives.PointF[] simplePath = { - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - }; - - var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); - - using (var image = provider.GetImage() as Image) - { - image.Mutate(x => x.BackgroundColor(Rgba32.Blue)); - - image.Mutate(x => x.Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs deleted file mode 100644 index da7c865b96..0000000000 --- a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Drawing -{ - [GroupOutput("Drawing")] - public class SolidFillBlendedShapesTests - { - public static IEnumerable modes = GetAllModeCombinations(); - - private static IEnumerable GetAllModeCombinations() - { - foreach (var composition in Enum.GetValues(typeof(PixelAlphaCompositionMode))) - { - foreach (var blending in Enum.GetValues(typeof(PixelColorBlendingMode))) - { - yield return new object[] { blending, composition }; - } - } - } - - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void _1DarkBlueRect_2BlendHotPinkRect( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : struct, IPixel - { - using (Image img = provider.GetImage()) - { - int scaleX = img.Width / 100; - int scaleY = img.Height / 100; - img.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) - ) - .Fill(new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode=composition }, - Color.HotPink, - new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) - ); - - VerifyImage(provider, blending, composition, img); - } - } - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : struct, IPixel - { - using (Image img = provider.GetImage()) - { - int scaleX = img.Width / 100; - int scaleY = img.Height / 100; - img.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); - img.Mutate( - x => x.Fill( - new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, - Color.HotPink, - new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); - img.Mutate( - x => x.Fill( - new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, - Color.Transparent, - new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) - ); - - VerifyImage(provider, blending, composition, img); - } - } - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : struct, IPixel - { - using (Image img = provider.GetImage()) - { - int scaleX = (img.Width / 100); - int scaleY = (img.Height / 100); - img.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); - img.Mutate( - x => x.Fill( - new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, - Color.HotPink, - new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); - - var transparentRed = Color.Red.WithAlpha(0.5f); - - img.Mutate( - x => x.Fill( - new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, - transparentRed, - new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) - ); - - VerifyImage(provider, blending, composition, img); ; - } - } - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void _1DarkBlueRect_2BlendBlackEllipse( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : struct, IPixel - { - using(Image dstImg = provider.GetImage(), srcImg = provider.GetImage()) - { - int scaleX = (dstImg.Width / 100); - int scaleY = (dstImg.Height / 100); - - dstImg.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); - - srcImg.Mutate( - x => x.Fill( - Color.Black, - new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - - dstImg.Mutate( - x => x.DrawImage(srcImg, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }) - ); - - VerifyImage(provider, blending, composition, dstImg); - } - } - - private static void VerifyImage( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition, - Image img) - where TPixel : struct, IPixel - { - img.DebugSave( - provider, - new { composition, blending }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - var comparer = ImageComparer.TolerantPercentage(0.01f, 3); - img.CompareFirstFrameToReferenceOutput(comparer, - provider, - new { composition, blending }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs deleted file mode 100644 index e6866c6579..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.Fonts; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Text; -using SixLabors.ImageSharp.Tests.Processing; -using SixLabors.Primitives; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Text -{ - public class DrawText : BaseImageOperationsExtensionTest - { - private readonly FontCollection FontCollection; - - private readonly Font Font; - - public DrawText() - { - this.FontCollection = new FontCollection(); - this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")).CreateFont(12); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSetAndNotPen() - { - this.operations.DrawText( - new TextGraphicsOptions(true), - "123", - this.Font, - Brushes.Solid(Color.Red), - null, - Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSetAndNotPenDefaultOptions() - { - this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSet() - { - this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSetDefaultOptions() - { - this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void FillsForEachACharacterWhenColorSet() - { - this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Color.Red, Vector2.Zero); - - var processor = this.Verify(0); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void FillsForEachACharacterWhenColorSetDefaultOptions() - { - this.operations.DrawText("123", this.Font, Color.Red, Vector2.Zero); - - var processor = this.Verify(0); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetAndNotBrush() - { - this.operations.DrawText( - new TextGraphicsOptions(true), - "123", - this.Font, - null, - Pens.Dash(Color.Red, 1), - Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetAndNotBrushDefaultOptions() - { - this.operations.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void DrawForEachACharacterWhenPenSet() - { - this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); - - this.Verify(0); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetDefaultOptions() - { - this.operations.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); - - var processor = this.Verify(0); - - Assert.Equal("123", processor.Text); - Assert.Equal(this.Font, processor.Font); - var penBrush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, penBrush.Color); - Assert.Equal(1, processor.Pen.StrokeWidth); - Assert.Equal(PointF.Empty, processor.Location); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetAndFillFroEachWhenBrushSet() - { - this.operations.DrawText( - new TextGraphicsOptions(true), - "123", - this.Font, - Brushes.Solid(Color.Red), - Pens.Dash(Color.Red, 1), - Vector2.Zero); - - var processor = this.Verify(0); - - Assert.Equal("123", processor.Text); - Assert.Equal(this.Font, processor.Font); - var brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(PointF.Empty, processor.Location); - var penBrush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, penBrush.Color); - Assert.Equal(1, processor.Pen.StrokeWidth); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs deleted file mode 100644 index a767a686ed..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Linq; -using System.Text; -using SixLabors.Fonts; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; - -using Xunit; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests.Drawing.Text -{ - [GroupOutput("Drawing/Text")] - public class DrawTextOnImageTests - { - private const string AB = "AB\nAB"; - - private const string TestText = "Sphinx of black quartz, judge my vow\n0123456789"; - - public static ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-5f); - public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(5e-4f); - - public DrawTextOnImageTests(ITestOutputHelper output) - { - this.Output = output; - } - - private ITestOutputHelper Output { get; } - - [Theory] - [WithSolidFilledImages(276, 336, "White", PixelTypes.Rgba32)] - public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688(TestImageProvider provider) - where TPixel : struct, IPixel - { - Font font = CreateFont("OpenSans-Regular.ttf", 36); - var color = Color.Black; - var text = "A short piece of text"; - - using (var img = provider.GetImage()) - { - // measure the text size - SizeF size = TextMeasurer.Measure(text, new RendererOptions(font)); - - //find out how much we need to scale the text to fill the space (up or down) - float scalingFactor = Math.Min(img.Width / size.Width, img.Height / size.Height); - - //create a new font - var scaledFont = new Font(font, scalingFactor * font.Size); - - var center = new PointF(img.Width / 2, img.Height / 2); - var textGraphicOptions = new TextGraphicsOptions(true) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - - img.Mutate(i => i.DrawText(textGraphicOptions, text, scaledFont, color, center)); - } - } - - [Theory] - [WithSolidFilledImages(1500, 500, "White", PixelTypes.Rgba32)] - public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688_2(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image img = provider.GetImage()) - { - Font font = CreateFont("OpenSans-Regular.ttf", 39); - string text = new string('a', 10000); // exception - // string text = "Hello"; // no exception - Rgba32 color = Rgba32.Black; - var point = new PointF(100, 100); - - img.Mutate(ctx => ctx.DrawText(text, font, color, point)); - } - } - - - [Theory] - [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] - [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] - [WithSolidFilledImages(400, 40, "White", PixelTypes.Rgba32, 20, 0, 0, "OpenSans-Regular.ttf", TestText)] - [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] - public void FontShapesAreRenderedCorrectly( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text) - where TPixel : struct, IPixel - { - Font font = CreateFont(fontName, fontSize); - var color = Color.Black; - - provider.VerifyOperation( - TextDrawingComparer, - img => - { - img.Mutate(c => c.DrawText(text, new Font(font, fontSize), color, new PointF(x, y))); - }, - $"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - /// - /// Based on: - /// https://github.com/SixLabors/ImageSharp/issues/572 - /// - [Theory] - [WithSolidFilledImages(2480, 3508, "White", PixelTypes.Rgba32)] - public void FontShapesAreRenderedCorrectly_LargeText( - TestImageProvider provider) - where TPixel : struct, IPixel - { - Font font = CreateFont("OpenSans-Regular.ttf", 36); - - var sb = new StringBuilder(); - string str = Repeat(" ", 78) + "THISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDS"; - sb.Append(str); - - string newLines = Repeat(Environment.NewLine, 80); - sb.Append(newLines); - - for (int i = 0; i < 10; i++) - { - sb.AppendLine(str); - } - - var textOptions = new TextGraphicsOptions - { - Antialias = true, - ApplyKerning = true, - VerticalAlignment = VerticalAlignment.Top, - HorizontalAlignment = HorizontalAlignment.Left, - }; - - var color = Color.Black; - - // Based on the reported 0.0270% difference with AccuracyMultiple = 8 - // We should avoid quality regressions leading to higher difference! - var comparer = ImageComparer.TolerantPercentage(0.03f); - - provider.VerifyOperation( - comparer, - img => - { - img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5))); - }, - false, - false); - } - - [Theory] - [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] - [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] - [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] - public void FontShapesAreRenderedCorrectlyWithAPen( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text) - where TPixel : struct, IPixel - { - Font font = CreateFont(fontName, fontSize); - var color = Color.Black; - - provider.VerifyOperation( - OutlinedTextDrawingComparer, - img => - { - img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.Solid(color, 1), new PointF(x, y))); - }, - $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] - [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] - [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] - public void FontShapesAreRenderedCorrectlyWithAPenPatterned( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text) - where TPixel : struct, IPixel - { - Font font = CreateFont(fontName, fontSize); - var color = Color.Black; - - provider.VerifyOperation( - OutlinedTextDrawingComparer, - img => - { - img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.DashDot(color, 3), new PointF(x, y))); - }, - $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "OpenSans-Regular.ttf")] - public void TextPositioningIsRobust(TestImageProvider provider, string fontName) - where TPixel : struct, IPixel - { - Font font = CreateFont(fontName, 30); - - string text = Repeat("Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n", - 20); - var textOptions = new TextGraphicsOptions(true) { WrapTextWidth = 1000 }; - - string details = fontName.Replace(" ", ""); - - // Based on the reported 0.1755% difference with AccuracyMultiple = 8 - // We should avoid quality regressions leading to higher difference! - var comparer = ImageComparer.TolerantPercentage(0.2f); - - provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, font, Color.Black, new PointF(10, 50)), - details, - comparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times)); - - private static string ToTestOutputDisplayText(string text) - { - string fnDisplayText = text.Replace("\n", ""); - fnDisplayText = fnDisplayText.Substring(0, Math.Min(fnDisplayText.Length, 4)); - return fnDisplayText; - } - - private static Font CreateFont(string fontName, int size) - { - var fontCollection = new FontCollection(); - string fontPath = TestFontUtilities.GetPath(fontName); - Font font = fontCollection.Install(fontPath).CreateFont(size); - return font; - } - - - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs deleted file mode 100644 index 0885611c67..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing.Text -{ - public class TextGraphicsOptionsTests - { - [Fact] - public void ExplicitCastOfGraphicsOptions() - { - var opt = new GraphicsOptions(false) - { - AntialiasSubpixelDepth = 99 - }; - - TextGraphicsOptions textOptions = opt; - - Assert.False(textOptions.Antialias); - Assert.Equal(99, textOptions.AntialiasSubpixelDepth); - } - - [Fact] - public void ImplicitCastToGraphicsOptions() - { - var textOptions = new TextGraphicsOptions(false) - { - AntialiasSubpixelDepth = 99 - }; - - var opt = (GraphicsOptions)textOptions; - - Assert.False(opt.Antialias); - Assert.Equal(99, opt.AntialiasSubpixelDepth); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs b/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs deleted file mode 100644 index 5ad7a1248d..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Tests.Drawing.Utils -{ - using System; - using System.Linq; - - using SixLabors.ImageSharp.Utils; - - using Xunit; - - public class QuickSortTests - { - public static readonly TheoryData Data = new TheoryData - { - new float[]{ 3, 2, 1 }, - new float[0], - new float[] { 42}, - new float[] { 1, 2}, - new float[] { 2, 1}, - new float[] { 5, 1, 2, 3, 0} - }; - - [Theory] - [MemberData(nameof(Data))] - public void Sort(float[] data) - { - float[] expected = data.ToArray(); - - Array.Sort(expected); - - QuickSort.Sort(data); - - Assert.Equal(expected, data); - } - - [Fact] - public void SortSlice() - { - float[] data = { 3, 2, 1, 0, -1 }; - - Span slice = data.AsSpan(1, 3); - QuickSort.Sort(slice); - float[] actual = slice.ToArray(); - float[] expected = { 0, 1, 2 }; - - Assert.Equal(actual, expected); - } - } -} diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 4f8475738b..12f7636a2e 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -71,22 +71,23 @@ namespace SixLabors.ImageSharp.Tests /// protected static readonly List Files = new List { - TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), - //TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Bad.BadEOF), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only - TestFile.Create(TestImages.Bmp.Car), +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), + // TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Bad.BadEOF), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only + TestFile.Create(TestImages.Bmp.Car), // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only // TestFile.Create(TestImages.Bmp.CoreHeader), // Perf: Enable for local testing only - TestFile.Create(TestImages.Png.Splash), + TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.SnakeGame), // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only @@ -104,10 +105,11 @@ namespace SixLabors.ImageSharp.Tests // TestFile.Create(TestImages.Png.FilterVar), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only - TestFile.Create(TestImages.Gif.Rings), + TestFile.Create(TestImages.Gif.Rings), // TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only }; +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index ecec6f0a7a..f63fc0a16a 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -3,15 +3,18 @@ using System; using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Formats.Bmp { using SixLabors.ImageSharp.Metadata; @@ -25,35 +28,65 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly string[] BitfieldsBmpFiles = BitFields; + private static BmpDecoder BmpDecoder => new BmpDecoder(); + public static readonly TheoryData RatioFiles = new TheoryData { - { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; [Theory] - [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, false)] + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - image.DebugSave(provider); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + } + + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + if (TestEnvironment.IsWindows) { image.CompareToOriginal(provider); } } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .Dispose(); + } + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + [WithFile(Bit16, PixelTypes.Rgba32)] + public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(BmpDecoder)); + Assert.IsType(ex.InnerException); } [Theory] [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -64,9 +97,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit16Inverted, PixelTypes.Rgba32)] [WithFile(Bit8Inverted, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -77,9 +110,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit1, PixelTypes.Rgba32)] [WithFile(Bit1Pal1, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); @@ -89,11 +122,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { @@ -105,9 +139,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit8, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -117,9 +151,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit16, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -129,9 +163,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -141,9 +175,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Rgba32v4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -155,11 +189,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(RLE4Delta, PixelTypes.Rgba32)] [WithFile(Rle4Delta320240, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { @@ -171,11 +206,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(RLE4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { @@ -190,7 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Rle8Delta320240, PixelTypes.Rgba32)] [WithFile(Rle8Blank160120, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { @@ -206,7 +242,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(RLE8Cut, PixelTypes.Rgba32)] [WithFile(RLE8Delta, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) { @@ -216,11 +252,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE8, PixelTypes.Rgba32)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(RLE8, PixelTypes.Rgba32, false)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] + [WithFile(RLE8, PixelTypes.Rgba32, true)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { + if (enforceDiscontiguousBuffers) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) { image.DebugSave(provider); @@ -229,12 +272,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE24, PixelTypes.Rgba32)] - [WithFile(RLE24Cut, PixelTypes.Rgba32)] - [WithFile(RLE24Delta, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(RLE24, PixelTypes.Rgba32, false)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, false)] + [WithFile(RLE24, PixelTypes.Rgba32, true)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, true)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider, bool enforceNonContiguous) + where TPixel : unmanaged, IPixel { + if (enforceNonContiguous) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); @@ -247,9 +298,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -261,9 +312,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgba, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -273,9 +324,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Rgba321010102, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -292,9 +343,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv2, PixelTypes.Rgba32)] [WithFile(CoreHeader, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -304,9 +355,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(WinBmpv3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -316,9 +367,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -329,9 +380,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(OversizedPalette, PixelTypes.Rgba32)] [WithFile(Rgb24LargePalette, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); if (TestEnvironment.IsWindows) @@ -343,27 +394,37 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] - public void BmpDecoder_ThrowsImageFormatException_OnInvalidPaletteSize(TestImageProvider provider) - where TPixel : struct, IPixel + public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - Assert.Throws( () => { using (provider.GetImage(new BmpDecoder())) { } }); + Assert.Throws(() => + { + using (provider.GetImage(BmpDecoder)) + { + } + }); } [Theory] [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] [WithFile(Rgb24png, PixelTypes.Rgba32)] public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Assert.Throws(() => { using (provider.GetImage(new BmpDecoder())) { } }); + Assert.Throws(() => + { + using (provider.GetImage(BmpDecoder)) + { + } + }); } [Theory] [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -373,9 +434,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -385,9 +446,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(WinBmpv4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -398,9 +459,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv5, PixelTypes.Rgba32)] [WithFile(V5Header, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -410,9 +471,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Pal8Offset, PixelTypes.Rgba32)] public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -422,9 +483,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(F, CommonNonDefaultPixelTypes)] public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -434,9 +495,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit8Palette4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -509,9 +570,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Os2v2Short, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -523,9 +584,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Os2v2, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -547,9 +608,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Os2BitmapArrayWarpd, PixelTypes.Rgba32)] [WithFile(Os2BitmapArrayPines, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 7412f70a11..235ecabf24 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -15,7 +15,6 @@ using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Formats.Bmp { using static TestImages.Bmp; @@ -32,8 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly TheoryData RatioFiles = new TheoryData { - { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; @@ -89,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp memStream.Position = 0; using (var output = Image.Load(memStream)) { - BmpMetadata meta = output.Metadata.GetFormatMetadata(BmpFormat.Instance); + BmpMetadata meta = output.Metadata.GetBmpMetadata(); Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); } @@ -100,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] @@ -109,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] @@ -117,8 +116,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] public void Encode_32Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - // if supportTransparency is false, a v3 bitmap header will be written - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + // If supportTransparency is false, a v3 bitmap header will be written. + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] @@ -126,59 +126,57 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] public void Encode_32Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] - // WinBmpv3 is a 24 bits per pixel image - [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] // WinBmpv3 is a 24 bits per pixel image. [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] public void Encode_24Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] public void Encode_24Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] public void Encode_16Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] public void Encode_16Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] - [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] public void Encode_8BitGray_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => TestBmpEncoderCore( provider, bitsPerPixel, supportTransparency: false); [Theory] - [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => TestBmpEncoderCore( provider, bitsPerPixel, @@ -187,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new WuQuantizer(256) + Quantizer = new WuQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); @@ -213,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -225,7 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new OctreeQuantizer(256) + Quantizer = new OctreeQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); @@ -240,14 +238,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(100); + TestBmpEncoderCore(provider, bitsPerPixel); + } private static void TestBmpEncoderCore( TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true, ImageComparer customComparer = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs index 25cf29406e..ccb57c35f1 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Bmp; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs deleted file mode 100644 index 7045d64508..0000000000 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; - -using SixLabors.ImageSharp.Formats.Bmp; -using Xunit; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests.Formats.Bmp -{ - using static TestImages.Bmp; - - public class BmpMetaDataTests - { - [Fact] - public void CloneIsDeep() - { - var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; - var clone = (BmpMetadata)meta.DeepClone(); - - clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; - - Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); - } - - [Theory] - [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] - [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] - [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] - [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] - [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] - [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] - [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] - [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] - public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - BmpMetadata bitmapMetaData = imageInfo.Metadata.GetFormatMetadata(BmpFormat.Instance); - Assert.NotNull(bitmapMetaData); - Assert.Equal(expectedInfoHeaderType, bitmapMetaData.InfoHeaderType); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs new file mode 100644 index 0000000000..9818f9d41f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Bmp +{ + using static TestImages.Bmp; + + public class BmpMetadataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var clone = (BmpMetadata)meta.DeepClone(); + + clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + } + + [Theory] + [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] + [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] + [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] + [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] + [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] + [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] + [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] + [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] + public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 62e9acf747..ca236e9146 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -1,29 +1,28 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using System.Linq; +using System.Reflection; + using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats { - using System; - using System.Reflection; - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Quantization; - using SixLabors.Memory; - public class GeneralFormatTests : FileTestBase { [Theory] [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] public void ResolutionShouldChange(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -42,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = file.CreateRgba32Image()) { - string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; + string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); } } @@ -57,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = file.CreateRgba32Image()) { - image.Save($"{path}/{file.FileName}"); + image.Save(Path.Combine(path, file.FileName)); } } } @@ -75,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bike, nameof(QuantizerNames), PixelTypes.Rgba32)] public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImageProvider provider, string quantizerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); @@ -92,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests private static IQuantizer GetQuantizer(string name) { PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); - return (IQuantizer)property.GetMethod.Invoke(null, new object[0]); + return (IQuantizer)property.GetMethod.Invoke(null, Array.Empty()); } [Fact] @@ -104,25 +103,30 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = file.CreateRgba32Image()) { - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) { image.SaveAsBmp(output); } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) { image.SaveAsJpeg(output); } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) { image.SaveAsPng(output); } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) { image.SaveAsGif(output); } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) + { + image.SaveAsTga(output); + } } } } @@ -167,38 +171,49 @@ namespace SixLabors.ImageSharp.Tests [InlineData(100, 100, "jpg")] [InlineData(100, 10, "jpg")] [InlineData(10, 100, "jpg")] - public void CanIdentifyImageLoadedFromBytes(int width, int height, string format) + [InlineData(100, 100, "tga")] + [InlineData(100, 10, "tga")] + [InlineData(10, 100, "tga")] + public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) { using (var memoryStream = new MemoryStream()) { - image.Save(memoryStream, GetEncoder(format)); + IImageFormat format = GetFormat(extension); + image.Save(memoryStream, format); memoryStream.Position = 0; IImageInfo imageInfo = Image.Identify(memoryStream); Assert.Equal(imageInfo.Width, width); Assert.Equal(imageInfo.Height, height); + memoryStream.Position = 0; + + imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); + + Assert.Equal(format, detectedFormat); } } } - private static IImageEncoder GetEncoder(string format) + [Fact] + public void IdentifyReturnsNullWithInvalidStream() { - switch (format) + byte[] invalid = new byte[10]; + + using (var memoryStream = new MemoryStream(invalid)) { - case "png": - return new PngEncoder(); - case "gif": - return new GifEncoder(); - case "bmp": - return new BmpEncoder(); - case "jpg": - return new JpegEncoder(); - default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); + IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); + + Assert.Null(imageInfo); + Assert.Null(format); } } + + private static IImageFormat GetFormat(string format) + { + return Configuration.Default.ImageFormats.FirstOrDefault(x => x.FileExtensions.Contains(format)); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 99dc2d06da..12d890357a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -2,13 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; using System.IO; -using SixLabors.ImageSharp.Advanced; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using Xunit; // ReSharper disable InconsistentNaming @@ -18,41 +21,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private static GifDecoder GifDecoder => new GifDecoder(); + public static readonly string[] MultiFrameTestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Kumin }; - public static readonly string[] BasicVerificationFiles = - { - TestImages.Gif.Cheers, - TestImages.Gif.Rings, - - // previously DecodeBadApplicationExtensionLength: - TestImages.Gif.Issues.BadAppExtLength, - TestImages.Gif.Issues.BadAppExtLength_2, - - // previously DecodeBadDescriptorDimensionsLength: - TestImages.Gif.Issues.BadDescriptorWidth - }; - - private static readonly Dictionary BasicVerificationFrameCount = - new Dictionary - { - [TestImages.Gif.Cheers] = 93, - [TestImages.Gif.Issues.BadDescriptorWidth] = 36, - }; - - public static readonly string[] BadAppExtFiles = - { - TestImages.Gif.Issues.BadAppExtLength, - TestImages.Gif.Issues.BadAppExtLength_2 - }; - [Theory] [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] public void Decode_VerifyAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -72,9 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { using (var stream = new UnmanagedMemoryStream(data, length)) { - var decoder = new GifDecoder(); - - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = GifDecoder.Decode(Configuration.Default, stream)) { Assert.Equal((200, 200), (image.Width, image.Height)); } @@ -85,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Trans, TestPixelTypes)] public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -95,15 +72,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } [Theory] - [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] - public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 93)] + [WithFile(TestImages.Gif.Rings, PixelTypes.Rgba32, 1)] + [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32, 36)] + public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) + where TPixel : unmanaged, IPixel { - if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) - { - expectedFrameCount = 1; - } - using (Image image = provider.GetImage()) { Assert.Equal(expectedFrameCount, image.Frames.Count); @@ -115,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First })) { @@ -126,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All })) { @@ -148,6 +122,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } + [Theory] + [WithFile(TestImages.Gif.ZeroSize, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.ZeroWidth, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.ZeroHeight, PixelTypes.Rgba32)] + public void Decode_WithInvalidDimensions_DoesThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(GifDecoder); + }); + Assert.NotNull(ex); + Assert.Contains("Width or height should not be 0", ex.Message); + } + + [Theory] + [WithFile(TestImages.Gif.MaxWidth, PixelTypes.Rgba32, 65535, 1)] + [WithFile(TestImages.Gif.MaxHeight, PixelTypes.Rgba32, 1, 65535)] + public void Decode_WithMaxDimensions_Works(TestImageProvider provider, int expectedWidth, int expectedHeight) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(GifDecoder)) + { + Assert.Equal(expectedWidth, image.Width); + Assert.Equal(expectedHeight, image.Height); + } + } + [Fact] public void CanDecodeIntermingledImages() { @@ -159,9 +162,62 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { ImageFrame first = kumin1.Frames[i]; ImageFrame second = kumin2.Frames[i]; - first.ComparePixelBufferTo(second.GetPixelSpan()); + + Assert.True(second.TryGetSinglePixelSpan(out Span secondSpan)); + + first.ComparePixelBufferTo(secondSpan); } } } + + // https://github.com/SixLabors/ImageSharp/issues/405 + [Theory] + [WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Issues.BadAppExtLength_2, PixelTypes.Rgba32)] + public void Issue405_BadApplicationExtensionBlockLength(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(GifDecoder)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(GifDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index d6d58eae7a..588f652548 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { public class GifEncoderTests @@ -20,23 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; [Theory] - [WithTestPatternImages(100, 100, TestPixelTypes)] - public void EncodeGeneratedPatterns(TestImageProvider provider) - where TPixel : struct, IPixel + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) + where TPixel : unmanaged, IPixel { + if (limitAllocationBuffer) + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + } + using (Image image = provider.GetImage()) { var encoder = new GifEncoder { // Use the palette quantizer without dithering to ensure results // are consistent - Quantizer = new WebSafePaletteQuantizer(false) + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -92,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif memStream.Position = 0; using (var output = Image.Load(memStream)) { - GifMetadata metadata = output.Metadata.GetFormatMetadata(GifFormat.Instance); + GifMetadata metadata = output.Metadata.GetGifMetadata(); Assert.Equal(1, metadata.Comments.Count); Assert.Equal("ImageSharp", metadata.Comments[0]); } @@ -103,14 +109,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var encoder = new GifEncoder { ColorTableMode = GifColorTableMode.Global, - Quantizer = new OctreeQuantizer(false) + Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -135,13 +141,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif inStream.Position = 0; var image = Image.Load(inStream); - GifMetadata metaData = image.Metadata.GetFormatMetadata(GifFormat.Instance); - GifFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetFormatMetadata(GifFormat.Instance); + GifMetadata metaData = image.Metadata.GetGifMetadata(); + GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata(); GifColorTableMode colorMode = metaData.ColorTableMode; var encoder = new GifEncoder { ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(frameMetaData.ColorTableLength) + Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength }) }; image.Save(outStream, encoder); @@ -150,16 +156,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif outStream.Position = 0; var clone = Image.Load(outStream); - GifMetadata cloneMetaData = clone.Metadata.GetFormatMetadata(GifFormat.Instance); - Assert.Equal(metaData.ColorTableMode, cloneMetaData.ColorTableMode); + GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata(); + Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode); // Gifiddle and Cyotek GifInfo say this image has 64 colors. - Assert.Equal(64, frameMetaData.ColorTableLength); + Assert.Equal(64, frameMetadata.ColorTableLength); for (int i = 0; i < image.Frames.Count; i++) { - GifFrameMetadata ifm = image.Frames[i].Metadata.GetFormatMetadata(GifFormat.Instance); - GifFrameMetadata cifm = clone.Frames[i].Metadata.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata ifm = image.Frames[i].Metadata.GetGifMetadata(); + GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata(); Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength); Assert.Equal(ifm.FrameDelay, cifm.FrameDelay); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs deleted file mode 100644 index b891c8ed20..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats.Gif; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Gif -{ - public class GifFrameMetaDataTests - { - [Fact] - public void CloneIsDeep() - { - var meta = new GifFrameMetadata - { - FrameDelay = 1, - DisposalMethod = GifDisposalMethod.RestoreToBackground, - ColorTableLength = 2 - }; - - var clone = (GifFrameMetadata)meta.DeepClone(); - - clone.FrameDelay = 2; - clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; - clone.ColorTableLength = 1; - - Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); - Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs new file mode 100644 index 0000000000..a3bc5d45c4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifFrameMetadataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifFrameMetadata + { + FrameDelay = 1, + DisposalMethod = GifDisposalMethod.RestoreToBackground, + ColorTableLength = 2 + }; + + var clone = (GifFrameMetadata)meta.DeepClone(); + + clone.FrameDelay = 2; + clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; + clone.ColorTableLength = 1; + + Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); + Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs deleted file mode 100644 index 8cb61235e3..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using System.IO; -using System.Linq; - -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Gif -{ - public class GifMetaDataTests - { - public static readonly TheoryData RatioFiles = - new TheoryData - { - { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, - { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - [Fact] - public void CloneIsDeep() - { - var meta = new GifMetadata - { - RepeatCount = 1, - ColorTableMode = GifColorTableMode.Global, - GlobalColorTableLength = 2, - Comments = new List { "Foo" } - }; - - var clone = (GifMetadata)meta.DeepClone(); - - clone.RepeatCount = 2; - clone.ColorTableMode = GifColorTableMode.Local; - clone.GlobalColorTableLength = 1; - - Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); - Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); - Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); - Assert.False(meta.Comments.Equals(clone.Comments)); - Assert.True(meta.Comments.SequenceEqual(clone.Comments)); - } - - [Fact] - public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() - { - var options = new GifDecoder - { - IgnoreMetadata = false - }; - - var testFile = TestFile.Create(TestImages.Gif.Rings); - - using (Image image = testFile.CreateRgba32Image(options)) - { - GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance); - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal("ImageSharp", metadata.Comments[0]); - } - } - - [Fact] - public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() - { - var options = new GifDecoder - { - IgnoreMetadata = true - }; - - var testFile = TestFile.Create(TestImages.Gif.Rings); - - using (Image image = testFile.CreateRgba32Image(options)) - { - GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance); - Assert.Equal(0, metadata.Comments.Count); - } - } - - [Fact] - public void Decode_CanDecodeLargeTextComment() - { - var options = new GifDecoder(); - var testFile = TestFile.Create(TestImages.Gif.LargeComment); - - using (Image image = testFile.CreateRgba32Image(options)) - { - GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } - } - - [Fact] - public void Encode_PreservesTextData() - { - var decoder = new GifDecoder(); - var testFile = TestFile.Create(TestImages.Gif.LargeComment); - - using (Image input = testFile.CreateRgba32Image(decoder)) - using (var memoryStream = new MemoryStream()) - { - input.Save(memoryStream, new GifEncoder()); - memoryStream.Position = 0; - - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) - { - GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs new file mode 100644 index 0000000000..ddb5608daf --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -0,0 +1,194 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifMetadataTests + { + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, + { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + + public static readonly TheoryData RepeatFiles = + new TheoryData + { + { TestImages.Gif.Cheers, 0 }, + { TestImages.Gif.Receipt, 1 }, + { TestImages.Gif.Rings, 1 } + }; + + [Fact] + public void CloneIsDeep() + { + var meta = new GifMetadata + { + RepeatCount = 1, + ColorTableMode = GifColorTableMode.Global, + GlobalColorTableLength = 2, + Comments = new List { "Foo" } + }; + + var clone = (GifMetadata)meta.DeepClone(); + + clone.RepeatCount = 2; + clone.ColorTableMode = GifColorTableMode.Local; + clone.GlobalColorTableLength = 1; + + Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); + Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); + Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); + Assert.False(meta.Comments.Equals(clone.Comments)); + Assert.True(meta.Comments.SequenceEqual(clone.Comments)); + } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var options = new GifDecoder + { + IgnoreMetadata = false + }; + + var testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateRgba32Image(options)) + { + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal("ImageSharp", metadata.Comments[0]); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + var options = new GifDecoder + { + IgnoreMetadata = true + }; + + var testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateRgba32Image(options)) + { + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(0, metadata.Comments.Count); + } + } + + [Fact] + public void Decode_CanDecodeLargeTextComment() + { + var options = new GifDecoder(); + var testFile = TestFile.Create(TestImages.Gif.LargeComment); + + using (Image image = testFile.CreateRgba32Image(options)) + { + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(2, metadata.Comments.Count); + Assert.Equal(new string('c', 349), metadata.Comments[0]); + Assert.Equal("ImageSharp", metadata.Comments[1]); + } + } + + [Fact] + public void Encode_PreservesTextData() + { + var decoder = new GifDecoder(); + var testFile = TestFile.Create(TestImages.Gif.LargeComment); + + using (Image input = testFile.CreateRgba32Image(decoder)) + using (var memoryStream = new MemoryStream()) + { + input.Save(memoryStream, new GifEncoder()); + memoryStream.Position = 0; + + using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + { + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(2, metadata.Comments.Count); + Assert.Equal(new string('c', 349), metadata.Comments[0]); + Assert.Equal("ImageSharp", metadata.Comments[1]); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RepeatFiles))] + public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); + } + } + + [Theory] + [MemberData(nameof(RepeatFiles))] + public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 9a15e1c1b3..9dba7179d9 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -10,15 +10,16 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; using Xunit; - -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats { public class ImageFormatManagerTests { public ImageFormatManager FormatsManagerEmpty { get; } + public ImageFormatManager DefaultFormatsManager { get; } public ImageFormatManagerTests() @@ -28,17 +29,19 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void IfAutoloadWellKnownFormatsIsTrueAllFormatsAreLoaded() + public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() { Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index 4b1abf9094..f55e46c3dc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -1,19 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -//#define BENCHMARKING - +// #define BENCHMARKING using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.Memory; -using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class Block8x8FTests @@ -31,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int x = 0; x < 20; x++) { - if (x < subX || x >= subX + 8 * horizontalFactor || y < subY || y >= subY + 8 * verticalFactor) + if (x < subX || x >= subX + (8 * horizontalFactor) || y < subY || y >= subY + (8 * verticalFactor)) { Assert.Equal(0, buffer[x, y]); } @@ -47,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) { BufferArea area = buffer.GetArea(5, 10, 8, 8); - block.Copy1x1Scale(area); + block.Copy1x1Scale(ref area.GetReferenceToOrigin(), area.Stride); Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[1, 0], buffer[6, 10]); @@ -75,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(100, 100, AllocationOptions.Clean)) { BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); - block.CopyTo(area, horizontalFactor, verticalFactor); + block.ScaledCopyTo(area, horizontalFactor, verticalFactor); for (int y = 0; y < 8 * verticalFactor; y++) { @@ -96,4 +93,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 21b9b6cab6..b3c911f566 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -2,8 +2,7 @@ // Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -//#define BENCHMARKING - +// #define BENCHMARKING using System; using System.Diagnostics; @@ -30,11 +29,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private bool SkipOnNonAvx2Runner() { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("AVX2 not supported, skipping!"); return true; } + return false; } @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var block = new Block8x8F(); + var block = default(Block8x8F); for (int i = 0; i < Block8x8F.Size; i++) { @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg () => { // Block8x8F block = new Block8x8F(); - var block = new float[64]; + float[] block = new float[64]; for (int i = 0; i < Block8x8F.Size; i++) { block[i] = i; @@ -90,8 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void Load_Store_FloatArray() { - var data = new float[Block8x8F.Size]; - var mirror = new float[Block8x8F.Size]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; for (int i = 0; i < Block8x8F.Size; i++) { @@ -102,9 +102,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var b = new Block8x8F(); + var b = default(Block8x8F); b.LoadFrom(data); - b.CopyTo(mirror); + b.ScaledCopyTo(mirror); }); Assert.Equal(data, mirror); @@ -115,8 +115,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public unsafe void Load_Store_FloatArray_Ptr() { - var data = new float[Block8x8F.Size]; - var mirror = new float[Block8x8F.Size]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; for (int i = 0; i < Block8x8F.Size; i++) { @@ -127,9 +127,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var b = new Block8x8F(); + var b = default(Block8x8F); Block8x8F.LoadFrom(&b, data); - Block8x8F.CopyTo(&b, mirror); + Block8x8F.ScaledCopyTo(&b, mirror); }); Assert.Equal(data, mirror); @@ -140,8 +140,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void Load_Store_IntArray() { - var data = new int[Block8x8F.Size]; - var mirror = new int[Block8x8F.Size]; + int[] data = new int[Block8x8F.Size]; + int[] mirror = new int[Block8x8F.Size]; for (int i = 0; i < Block8x8F.Size; i++) { @@ -152,9 +152,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var v = new Block8x8F(); + var v = default(Block8x8F); v.LoadFrom(data); - v.CopyTo(mirror); + v.ScaledCopyTo(mirror); }); Assert.Equal(data, mirror); @@ -168,14 +168,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(Create8x8FloatData()); - var dest = new Block8x8F(); + var dest = default(Block8x8F); source.TransposeInto(ref dest); - var actual = new float[64]; - dest.CopyTo(actual); + float[] actual = new float[64]; + dest.ScaledCopyTo(actual); Assert.Equal(expected, actual); } @@ -206,12 +206,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static float[] Create8x8ColorCropTestData() { - var result = new float[64]; + float[] result = new float[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = -300 + i * 100 + j * 10; + result[(i * 8) + j] = -300 + (i * 100) + (j * 10); } } @@ -230,8 +230,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F dest = block; dest.NormalizeColorsInplace(255); - var array = new float[64]; - dest.CopyTo(array); + float[] array = new float[64]; + dest.ScaledCopyTo(array); this.Output.WriteLine("Result:"); this.PrintLinearData(array); foreach (float val in array) @@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg expected.RoundInplace(); Block8x8F actual = source; - actual.NormalizeColorsAndRoundInplaceAvx2(255); + actual.NormalizeColorsAndRoundInplaceVector8(255); this.Output.WriteLine(expected.ToString()); this.Output.WriteLine(actual.ToString()); @@ -269,10 +269,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public unsafe void Quantize(int seed) { - var block = new Block8x8F(); + var block = default(Block8x8F); block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - var qt = new Block8x8F(); + var qt = default(Block8x8F); qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); var unzig = ZigZag.CreateUnzigTable(); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index da75e059f4..af8ba83c35 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -58,10 +58,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { sum += Block8x8.GetScalarAt(&block, i); } + Assert.Equal(sum, 64 * 63 / 2); } - [Fact] public void AsFloatBlock() { @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void IndexerXY() { Block8x8 block = default; - block[8 * 3 + 5] = 42; + block[(8 * 3) + 5] = 42; short value = block[5, 3]; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 91e2f43d35..683a79a8f4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -1,4 +1,6 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -7,6 +9,7 @@ using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public static class DCTTests @@ -19,22 +22,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void iDCT2D8x4_LeftPart() + public void IDCT2D8x4_LeftPart() { float[] sourceArray = Create8x8FloatData(); var expectedDestArray = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray, expectedDestArray); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray, expectedDestArray); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(sourceArray); - var dest = new Block8x8F(); + var dest = default(Block8x8F); FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); var actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); + dest.ScaledCopyTo(actualDestArray); this.Print8x8Data(expectedDestArray); this.Output.WriteLine("**************"); @@ -44,22 +47,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void iDCT2D8x4_RightPart() + public void IDCT2D8x4_RightPart() { float[] sourceArray = Create8x8FloatData(); var expectedDestArray = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(sourceArray); - var dest = new Block8x8F(); + var dest = default(Block8x8F); FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); var actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); + dest.ScaledCopyTo(actualDestArray); this.Print8x8Data(expectedDestArray); this.Output.WriteLine("**************"); @@ -106,25 +109,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 1f); } - [Theory] [InlineData(1)] [InlineData(2)] public void FDCT8x4_LeftPart(int seed) { Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); var expectedDest = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src, expectedDest); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest); FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); var actualDest = new float[64]; - destBlock.CopyTo(actualDest); + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } @@ -135,18 +137,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FDCT8x4_RightPart(int seed) { Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); var expectedDest = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); var actualDest = new float[64]; - destBlock.CopyTo(actualDest); + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } @@ -157,24 +159,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void TransformFDCT(int seed) { Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); var expectedDest = new float[64]; var temp1 = new float[64]; - var temp2 = new Block8x8F(); + var temp2 = default(Block8x8F); - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); var actualDest = new float[64]; - destBlock.CopyTo(actualDest); + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs index 7c42af5961..978ee7b2aa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class GenericBlock8x8Tests { public static Image CreateTestImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var image = new Image(10, 10); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -36,12 +36,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 0); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet); TPixel a = s.Frames.RootFrame[0, 0]; TPixel b = d[0, 0]; @@ -60,12 +61,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] public void LoadAndStretchCorners_WithOffset(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 7); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet); Assert.Equal(s[6, 7], d[0, 0]); Assert.Equal(s[6, 8], d[0, 1]); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index caaad73c9f..27c612aee8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; -using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using Xunit; using Xunit.Abstractions; @@ -99,23 +99,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromYCbCrSimdAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("No AVX2 present, skipping test!"); return; } - //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - + // JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimdAvx2(8), + new JpegColorConverter.FromYCbCrSimdVector8(8), 3, inputBufferLength, resultBufferLength, seed); } - [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) @@ -129,9 +127,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } // Benchmark, for local execution only - //[Theory] - //[InlineData(false)] - //[InlineData(true)] + // [Theory] + // [InlineData(false)] + // [InlineData(true)] public void BenchmarkYCbCr(bool simd) { int count = 2053; @@ -289,14 +287,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } // no need to dispose when buffer is not array owner var memory = new Memory(values); - var source = new MemorySource(memory); + var source = MemoryGroup.Wrap(memory); buffers[i] = new Buffer2D(source, values.Length, 1); } + return new JpegColorConverter.ComponentValues(buffers, 0); } @@ -308,7 +307,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { ValidateRgbToYCbCrConversion( - JpegColorConverter.GetConverter(colorSpace,8), + JpegColorConverter.GetConverter(colorSpace, 8), componentCount, inputBufferLength, resultBufferLength, @@ -333,4 +332,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 2485561f1e..57051a9d7b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -1,41 +1,58 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. - +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class JpegDecoderTests { [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgba32, true)] + public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - if (SkipTest(provider)) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - // skipping to avoid OutOfMemoryException on CI - return; - } + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixels(1000 * 8); + } + + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput( - this.GetImageComparer(provider), + GetImageComparer(provider), provider, appendPixelTypeToFileName: false); } + + string providerDump = BasicSerializer.Serialize(provider); + RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); + + // RemoteExecutor.Invoke( + // RunTest, + // providerDump, + // enforceDiscontiguousBuffers ? "Disco" : string.Empty) + // .Dispose(); } [Theory] [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] - public void UnrecoverableImagesShouldThrowCorrectError(TestImageProvider provider) - where TPixel : struct, IPixel => Assert.Throws(provider.GetImage); + public void UnrecoverableImage_Throws_InvalidImageContentException(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(provider.GetImage); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 4a3ef9b956..a01f4d46cd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -13,11 +13,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Ycck, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Turtle420, TestImages.Jpeg.Baseline.Testorig420, // BUG: The following image has a high difference compared to the expected output: 1.0096% - // TestImages.Jpeg.Baseline.Jpeg420Small, - + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, @@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.InvalidAPP0721, TestImages.Jpeg.Issues.ExifGetString750Load, TestImages.Jpeg.Issues.ExifGetString750Transform, + TestImages.Jpeg.Issues.BadSubSampling1076, // LibJpeg can open this despite the invalid density units. TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, @@ -38,6 +39,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // LibJpeg can open this despite incorrect colorspace metadata. TestImages.Jpeg.Issues.IncorrectColorspace855, + // LibJpeg can open this despite the invalid subsampling units. + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, + // High depth images TestImages.Jpeg.Baseline.Testorig12bit, }; @@ -61,8 +65,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C }; - public static string[] UnrecoverableTestJpegs = { - + public static string[] UnrecoverableTestJpegs = + { TestImages.Jpeg.Issues.CriticalEOF214, TestImages.Jpeg.Issues.Fuzz.NullReferenceException797, TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, @@ -71,7 +75,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824A, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824B, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824E, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824F, @@ -93,9 +96,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Baseline: [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 1.1f / 100, + [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + // Progressive: [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs deleted file mode 100644 index 1d5c3e27a4..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - using System; - using System.Runtime.CompilerServices; - - using SixLabors.ImageSharp.Formats.Jpeg; - using SixLabors.ImageSharp.Metadata; - - public partial class JpegDecoderTests - { - // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. - // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) - public static readonly TheoryData MetaDataTestData = - new TheoryData - { - { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - - { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - }; - - public static readonly TheoryData RatioFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, - { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; - - public static readonly TheoryData QualityFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 }, - { TestImages.Jpeg.Issues.IncorrectQuality845, 99 } - }; - - [Theory] - [MemberData(nameof(MetaDataTestData))] - public void MetaDataIsParsedCorrectly( - bool useIdentify, - string imagePath, - int expectedPixelSize, - bool exifProfilePresent, - bool iccProfilePresent) - { - TestMetaDataImpl( - useIdentify, - JpegDecoder, - imagePath, - expectedPixelSize, - exifProfilePresent, - iccProfilePresent); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - - [Theory] - [MemberData(nameof(QualityFiles))] - public void Identify_VerifyQuality(string imagePath, int quality) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); - JpegMetadata meta = image.Metadata.GetFormatMetadata(JpegFormat.Instance); - Assert.Equal(quality, meta.Quality); - } - } - - [Theory] - [MemberData(nameof(QualityFiles))] - public void Decode_VerifyQuality(string imagePath, int quality) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) - { - JpegMetadata meta = image.Metadata.GetFormatMetadata(JpegFormat.Instance); - Assert.Equal(quality, meta.Quality); - } - } - } - - private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = useIdentify - ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) - : decoder.Decode(Configuration.Default, stream); - - test(imageInfo); - } - } - - private static void TestMetaDataImpl( - bool useIdentify, - IImageDecoder decoder, - string imagePath, - int expectedPixelSize, - bool exifProfilePresent, - bool iccProfilePresent) - { - TestImageInfo( - imagePath, - decoder, - useIdentify, - imageInfo => - { - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.PixelType); - - if (useIdentify) - { - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - } - else - { - // When full Image decoding is performed, BitsPerPixel will match TPixel - int bpp32 = Unsafe.SizeOf() * 8; - Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); - } - - ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; - - if (exifProfilePresent) - { - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - } - else - { - Assert.Null(exifProfile); - } - - IccProfile iccProfile = imageInfo.Metadata.IccProfile; - - if (iccProfilePresent) - { - Assert.NotNull(iccProfile); - Assert.NotEmpty(iccProfile.Entries); - } - else - { - Assert.Null(iccProfile); - } - }); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void IgnoreMetaData_ControlsWhetherMetaDataIsParsed(bool ignoreMetaData) - { - var decoder = new JpegDecoder { IgnoreMetadata = ignoreMetaData }; - - // Snake.jpg has both Exif and ICC profiles defined: - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); - - using (Image image = testFile.CreateRgba32Image(decoder)) - { - if (ignoreMetaData) - { - Assert.Null(image.Metadata.ExifProfile); - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.ExifProfile); - Assert.NotNull(image.Metadata.IccProfile); - } - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) - { - TestImageInfo(TestImages.Jpeg.Baseline.Floorplan, JpegDecoder, useIdentify, - imageInfo => - { - Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(300, imageInfo.Metadata.VerticalResolution); - }); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) - { - TestImageInfo(TestImages.Jpeg.Baseline.Jpeg420Exif, JpegDecoder, useIdentify, - imageInfo => - { - Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(72, imageInfo.Metadata.VerticalResolution); - }); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs new file mode 100644 index 0000000000..c2fc320aff --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -0,0 +1,265 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.Metadata; + + public partial class JpegDecoderTests + { + // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. + // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) + public static readonly TheoryData MetadataTestData = + new TheoryData + { + { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; + + public static readonly TheoryData QualityFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 }, + { TestImages.Jpeg.Issues.IncorrectQuality845, 99 } + }; + + [Theory] + [MemberData(nameof(MetadataTestData))] + public void MetadataIsParsedCorrectly( + bool useIdentify, + string imagePath, + int expectedPixelSize, + bool exifProfilePresent, + bool iccProfilePresent) + { + TestMetadataImpl( + useIdentify, + JpegDecoder, + imagePath, + expectedPixelSize, + exifProfilePresent, + iccProfilePresent); + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Identify_VerifyQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Decode_VerifyQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } + } + } + + private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = useIdentify + ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) + : decoder.Decode(Configuration.Default, stream); + + test(imageInfo); + } + } + + private static void TestMetadataImpl( + bool useIdentify, + IImageDecoder decoder, + string imagePath, + int expectedPixelSize, + bool exifProfilePresent, + bool iccProfilePresent) + { + TestImageInfo( + imagePath, + decoder, + useIdentify, + imageInfo => + { + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.PixelType); + + if (useIdentify) + { + Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); + } + else + { + // When full Image decoding is performed, BitsPerPixel will match TPixel + int bpp32 = Unsafe.SizeOf() * 8; + Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); + } + + ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; + + if (exifProfilePresent) + { + Assert.NotNull(exifProfile); + Assert.NotEmpty(exifProfile.Values); + } + else + { + Assert.Null(exifProfile); + } + + IccProfile iccProfile = imageInfo.Metadata.IccProfile; + + if (iccProfilePresent) + { + Assert.NotNull(iccProfile); + Assert.NotEmpty(iccProfile.Entries); + } + else + { + Assert.Null(iccProfile); + } + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) + { + var decoder = new JpegDecoder { IgnoreMetadata = ignoreMetadata }; + + // Snake.jpg has both Exif and ICC profiles defined: + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); + + using (Image image = testFile.CreateRgba32Image(decoder)) + { + if (ignoreMetadata) + { + Assert.Null(image.Metadata.ExifProfile); + Assert.Null(image.Metadata.IccProfile); + } + else + { + Assert.NotNull(image.Metadata.ExifProfile); + Assert.NotNull(image.Metadata.IccProfile); + } + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) + { + TestImageInfo( + TestImages.Jpeg.Baseline.Floorplan, + JpegDecoder, + useIdentify, + imageInfo => + { + Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(300, imageInfo.Metadata.VerticalResolution); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) + { + TestImageInfo( + TestImages.Jpeg.Baseline.Jpeg420Exif, + JpegDecoder, + useIdentify, + imageInfo => + { + Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(72, imageInfo.Metadata.VerticalResolution); + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 77bc9f5404..4ecf987e98 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class JpegDecoderTests @@ -12,26 +14,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, true)] + public void DecodeProgressiveJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - if (SkipTest(provider)) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - // skipping to avoid OutOfMemoryException on CI - return; - } + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + } + + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider, nonContiguousBuffersStr); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( - this.GetImageComparer(provider), + GetImageComparer(provider), provider, appendPixelTypeToFileName: false); } + + string providerDump = BasicSerializer.Serialize(provider); + + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .Dispose(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index a9cddebc85..a35bb177ce 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -4,11 +4,12 @@ using System; using System.IO; using System.Linq; - +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -23,10 +24,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; private const float BaselineTolerance = 0.001F / 100; + private const float ProgressiveTolerance = 0.2F / 100; - private ImageComparer GetImageComparer(TestImageProvider provider) - where TPixel : struct, IPixel + private static ImageComparer GetImageComparer(TestImageProvider provider) + where TPixel : unmanaged, IPixel { string file = provider.SourceFileOrDescription; @@ -86,35 +88,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - if (SkipTest(provider)) - { - return; - } - - // For 32 bit test environments: - provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); - - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false); - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + appendPixelTypeToFileName: false); + } - provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(JpegDecoder)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); } // DEBUG ONLY! // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // into "\tests\Images\ActualOutput\JpegDecoderTests\" - //[Theory] - //[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] - public void ValidateProgressivePdfJsOutput(TestImageProvider provider, + // [Theory] + // [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] + public void ValidateProgressivePdfJsOutput( + TestImageProvider provider, string pdfJsOriginalResultImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm string pdfJsOriginalResultPath = Path.Combine( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 639ec520ec..6cbdb83fcd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,9 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -15,31 +20,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class JpegEncoderTests { public static readonly TheoryData QualityFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Calliphora, 80}, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData - { - { JpegSubsample.Ratio420, 40 }, - { JpegSubsample.Ratio420, 60 }, - { JpegSubsample.Ratio420, 100 }, - - { JpegSubsample.Ratio444, 40 }, - { JpegSubsample.Ratio444, 60 }, - { JpegSubsample.Ratio444, 100 }, - }; + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; public static readonly TheoryData RatioFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, - { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; [Theory] [MemberData(nameof(QualityFiles))] @@ -57,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg memStream.Position = 0; using (var output = Image.Load(memStream)) { - JpegMetadata meta = output.Metadata.GetFormatMetadata(JpegFormat.Instance); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); } } @@ -72,13 +76,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, subsample, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, subsample, quality); + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)] + [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegSubsample subsample) + where TPixel : unmanaged, IPixel + { + ImageComparer comparer = subsample == JpegSubsample.Ratio444 + ? ImageComparer.TolerantPercentage(0.1f) + : ImageComparer.TolerantPercentage(5f); + + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + TestJpegEncoderCore(provider, subsample, 100, comparer); + } /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation @@ -106,25 +127,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void TestJpegEncoderCore( TestImageProvider provider, JpegSubsample subsample, - int quality = 100) - where TPixel : struct, IPixel + int quality = 100, + ImageComparer comparer = null) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder { - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; - var encoder = new JpegEncoder - { - Subsample = subsample, - Quality = quality - }; - string info = $"{subsample}-Q{quality}"; - ImageComparer comparer = GetComparer(quality, subsample); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); - } + comparer ??= GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } [Fact] @@ -197,5 +219,70 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } + + [Fact] + public void Encode_PreservesIptcProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IptcProfile = new IptcProfile(); + input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); + var encoder = new JpegEncoder(); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IptcProfile actual = output.Metadata.IptcProfile; + Assert.NotNull(actual); + IEnumerable values = input.Metadata.IptcProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesExifProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.ExifProfile = new ExifProfile(); + input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); + var encoder = new JpegEncoder(); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile actual = output.Metadata.ExifProfile; + Assert.NotNull(actual); + IReadOnlyList values = input.Metadata.ExifProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesIccProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); + var encoder = new JpegEncoder(); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IccProfile actual = output.Metadata.IccProfile; + Assert.NotNull(actual); + IccProfile values = input.Metadata.IccProfile; + Assert.Equal(values.Entries, actual.Entries); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index b3219115db..32481e1f51 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) { @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void DoProcessorStep(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string imageFile = provider.SourceFileOrDescription; using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) @@ -60,11 +60,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg SaveBuffer(cp[2], provider); } } - + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void PostProcess(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string imageFile = provider.SourceFileOrDescription; using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) @@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs deleted file mode 100644 index 59ad571f49..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats.Jpeg; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - public class JpegMetaDataTests - { - [Fact] - public void CloneIsDeep() - { - var meta = new JpegMetadata { Quality = 50 }; - var clone = (JpegMetadata)meta.DeepClone(); - - clone.Quality = 99; - - Assert.False(meta.Quality.Equals(clone.Quality)); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs new file mode 100644 index 0000000000..50a2a44163 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class JpegMetadataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new JpegMetadata { Quality = 50 }; + var clone = (JpegMetadata)meta.DeepClone(); + + clone.Quality = 99; + + Assert.False(meta.Quality.Equals(clone.Quality)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs index 3d09f4b383..a6f80f5581 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.IO; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void RunDumpJpegCoeffsTool() { - if (!TestEnvironment.IsWindows) return; + if (!TestEnvironment.IsWindows) + { + return; + } string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); @@ -27,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] public void ExtractSpectralData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.IsWindows) { @@ -49,4 +55,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index 1d7ca746f6..6e04610d5a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -77,6 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); } + this.Output.WriteLine(sb.ToString()); } @@ -86,6 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) }, { TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) }, { TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) }, + // TODO: Find Ycck or Cmyk images with different subsampling { TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) }, { TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) }, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs index c908abc505..fb09065b0a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Text; @@ -19,25 +19,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void ProfileResolverHasCorrectJFifMarker() { - Assert.Equal(JFifMarker, ProfileResolver.JFifMarker); + Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectExifMarker() { - Assert.Equal(ExifMarker, ProfileResolver.ExifMarker); + Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectIccMarker() { - Assert.Equal(IccMarker, ProfileResolver.IccMarker); + Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectAdobeMarker() { - Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker); + Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); } [Fact] @@ -76,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 60a019c290..f8afb3d0be 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] dest = new float[64]; float[] temp = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D_llm(dest, src, temp); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(dest, src, temp); this.CompareBlocks(original, src, 0.1f); } @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] dest = new float[64]; - ReferenceImplementations.GT_FloatingPoint_DCT.iDCT8x8GT(floatSrc, dest); + ReferenceImplementations.GT_FloatingPoint_DCT.IDCT8x8GT(floatSrc, dest); this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index f16d04bf60..ca40403805 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; @@ -8,6 +9,7 @@ using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class ReferenceImplementationsTests diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index d5a1fb7ba0..3e125adac1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,15 +1,19 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class SpectralJpegTests @@ -41,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory(Skip = "Debug only, enable manually!")] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); @@ -59,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.IsWindows) { @@ -78,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.VerifySpectralCorrectnessImpl(provider, imageSharpData); } } - + private void VerifySpectralCorrectnessImpl( TestImageProvider provider, LibJpegTools.SpectralData imageSharpData) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); @@ -110,8 +114,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length; } + averageDifference /= componentCount; tolerance /= 64; // fair enough? @@ -123,4 +128,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.True(totalDifference < tolerance); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 20830a33f5..b7cf6a8406 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Diagnostics; using System.IO; @@ -14,11 +12,13 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { public class JpegFixture : MeasureFixture { - public JpegFixture(ITestOutputHelper output) : base(output) + public JpegFixture(ITestOutputHelper output) + : base(output) { } @@ -30,9 +30,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = i * 10 + j; + result[(i * 8) + j] = (i * 10) + j; } } + return result; } @@ -44,9 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = i * 10 + j; + result[(i * 8) + j] = (i * 10) + j; } } + return result; } @@ -58,14 +60,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - short val = (short)(i * 10 + j); + short val = (short)((i * 10) + j); if ((i + j) % 2 == 0) { val *= -1; } - result[i * 8 + j] = val; + + result[(i * 8) + j] = val; } } + return result; } @@ -78,9 +82,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = rnd.Next(minValue, maxValue); + result[(i * 8) + j] = rnd.Next(minValue, maxValue); } } + return result; } @@ -99,9 +104,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils val *= maxValue - minValue; val += minValue; - result[i * 8 + j] = (float)val; + result[(i * 8) + j] = (float)val; } } + return result; } @@ -120,8 +126,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - bld.Append($"{data[i * 8 + j],3} "); + bld.Append($"{data[(i * 8) + j],3} "); } + bld.AppendLine(); } @@ -132,13 +139,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal void PrintLinearData(Span data, int count = -1) { - if (count < 0) count = data.Length; + if (count < 0) + { + count = data.Length; + } var sb = new StringBuilder(); for (int i = 0; i < count; i++) { sb.Append($"{data[i],3} "); } + this.Output.WriteLine(sb.ToString()); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 91cd80d144..b9526994eb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { @@ -62,8 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils var result = new ComponentData( c.WidthInBlocks, c.HeightInBlocks, - index - ); + index); for (int y = 0; y < result.HeightInBlocks; y++) { @@ -89,6 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.WriteToImage(bx, by, result); } } + return result; } @@ -106,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Rgba32 color = default; color.FromVector4(v); - int yy = by * 8 + y; - int xx = bx * 8 + x; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; image[xx, yy] = color; } } @@ -115,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal float GetBlockValue(Block8x8 block, int x, int y) { - float d = (this.MaxVal - this.MinVal); + float d = this.MaxVal - this.MinVal; float val = block[y, x]; val -= this.MinVal; val /= d; @@ -136,9 +135,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks && this.WidthInBlocks == other.WidthInBlocks; - //&& this.MinVal == other.MinVal - //&& this.MaxVal == other.MaxVal; - if (!ok) return false; + if (!ok) + { + return false; + } for (int y = 0; y < this.HeightInBlocks; y++) { @@ -146,31 +146,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { Block8x8 a = this.SpectralBlocks[x, y]; Block8x8 b = other.SpectralBlocks[x, y]; - if (!a.Equals(b)) return false; + if (!a.Equals(b)) + { + return false; + } } } + return true; } public override bool Equals(object obj) { - if (obj is null) return false; - if (object.ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj is null) + { + return false; + } + + if (object.ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + return this.Equals((ComponentData)obj); } public override int GetHashCode() { - unchecked - { - int hashCode = this.Index; - hashCode = (hashCode * 397) ^ this.HeightInBlocks; - hashCode = (hashCode * 397) ^ this.WidthInBlocks; - hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); - hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); - return hashCode; - } + return HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal); } public ref Block8x8 GetBlockReference(int column, int row) @@ -180,12 +188,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static bool operator ==(ComponentData left, ComponentData right) { - return Object.Equals(left, right); + return object.Equals(left, right); } public static bool operator !=(ComponentData left, ComponentData right) { - return !Object.Equals(left, right); + return !object.Equals(left, right); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs index ac9e2835c2..0fce671e51 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -12,7 +12,6 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - internal static partial class LibJpegTools { /// @@ -40,7 +39,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public Image TryCreateRGBSpectralImage() { - if (this.ComponentCount != 3) return null; + if (this.ComponentCount != 3) + { + return null; + } LibJpegTools.ComponentData c0 = this.Components[0]; LibJpegTools.ComponentData c1 = this.Components[1]; @@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.WriteToImage(bx, by, result); } } + return result; } @@ -73,9 +76,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Block8x8 block1 = c1.SpectralBlocks[bx, by]; Block8x8 block2 = c2.SpectralBlocks[bx, by]; - float d0 = (c0.MaxVal - c0.MinVal); - float d1 = (c1.MaxVal - c1.MinVal); - float d2 = (c2.MaxVal - c2.MinVal); + float d0 = c0.MaxVal - c0.MinVal; + float d1 = c1.MaxVal - c1.MinVal; + float d2 = c2.MaxVal - c2.MinVal; for (int y = 0; y < 8; y++) { @@ -89,8 +92,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Rgba32 color = default; color.FromVector4(v); - int yy = by * 8 + y; - int xx = bx * 8 + x; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; image[xx, yy] = color; } } @@ -117,8 +120,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { LibJpegTools.ComponentData a = this.Components[i]; LibJpegTools.ComponentData b = other.Components[i]; - if (!a.Equals(b)) return false; + if (!a.Equals(b)) + { + return false; + } } + return true; } @@ -151,4 +158,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs index 31779df453..826335b652 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -1,8 +1,11 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; -using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; using System.Numerics; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs index 58fa4231e6..fc0540c64a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -18,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// internal static class AccurateDCT { - private static double[,] CosLut = InitCosLut(); + private static readonly double[,] CosLut = InitCosLut(); public static Block8x8 TransformIDCT(ref Block8x8 block) { @@ -29,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static void TransformIDCTInplace(Span span) { - var temp = new Block8x8(); + var temp = default(Block8x8); temp.LoadFrom(span); Block8x8 result = TransformIDCT(ref temp); result.CopyTo(span); @@ -44,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static void TransformFDCTInplace(Span span) { - var temp = new Block8x8(); + var temp = default(Block8x8); temp.LoadFrom(span); Block8x8 result = TransformFDCT(ref temp); result.CopyTo(span); @@ -56,19 +59,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils double tmp, tmp2; Block8x8F res = default; - for (y=0; y<8; y++) { - for (x=0; x<8; x++) { + for (y = 0; y < 8; y++) + { + for (x = 0; x < 8; x++) + { tmp = 0.0; - for (v=0; v<8; v++) { + for (v = 0; v < 8; v++) + { tmp2 = 0.0; - for (u=0; u<8; u++) { - tmp2 += block[v * 8 + u] * CosLut[x, u]; + for (u = 0; u < 8; u++) + { + tmp2 += block[(v * 8) + u] * CosLut[x, u]; } + tmp += CosLut[y, v] * tmp2; } - res[y * 8 + x] = (float)tmp; + + res[(y * 8) + x] = (float)tmp; } } + return res; } @@ -88,11 +98,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils tmp2 = 0.0; for (x = 0; x < 8; x++) { - tmp2 += block[y * 8 + x] * CosLut[x,u]; + tmp2 += block[(y * 8) + x] * CosLut[x, u]; } + tmp += CosLut[y, v] * tmp2; } - res[v * 8 + u] = (float) tmp; + + res[(v * 8) + u] = (float)tmp; } } @@ -101,20 +113,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils private static double[,] InitCosLut() { - var coslu = new double[8, 8]; + double[,] coslu = new double[8, 8]; int a, b; double tmp; for (a = 0; a < 8; a++) - for (b = 0; b < 8; b++) { - tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); - if (b == 0) + for (b = 0; b < 8; b++) { - tmp /= Math.Sqrt(2.0); + tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); + if (b == 0) + { + tmp /= Math.Sqrt(2.0); + } + + coslu[a, b] = tmp * 0.5; } - coslu[a, b] = tmp * 0.5; } + return coslu; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs index 3742e45bdc..1adcf0bc07 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs @@ -1,7 +1,9 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -9,24 +11,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Non-optimized method ported from: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L446 - /// + /// /// *** Paper *** /// Plonka, Gerlind, and Manfred Tasche. "Fast and numerically stable algorithms for discrete cosine transforms." Linear algebra and its applications 394 (2005) : 309 - 345. /// internal static class GT_FloatingPoint_DCT { - public static void idct81d_GT(Span src, Span dst) + public static void Idct81d_GT(Span src, Span dst) { for (int i = 0; i < 8; i++) { float mx00 = 1.4142135623731f * src[0]; - float mx01 = 1.38703984532215f * src[1] + 0.275899379282943f * src[7]; - float mx02 = 1.30656296487638f * src[2] + 0.541196100146197f * src[6]; - float mx03 = 1.17587560241936f * src[3] + 0.785694958387102f * src[5]; + float mx01 = (1.38703984532215f * src[1]) + (0.275899379282943f * src[7]); + float mx02 = (1.30656296487638f * src[2]) + (0.541196100146197f * src[6]); + float mx03 = (1.17587560241936f * src[3]) + (0.785694958387102f * src[5]); float mx04 = 1.4142135623731f * src[4]; - float mx05 = -0.785694958387102f * src[3] + 1.17587560241936f * src[5]; - float mx06 = 0.541196100146197f * src[2] - 1.30656296487638f * src[6]; - float mx07 = -0.275899379282943f * src[1] + 1.38703984532215f * src[7]; + float mx05 = (-0.785694958387102f * src[3]) + (1.17587560241936f * src[5]); + float mx06 = (0.541196100146197f * src[2]) - (1.30656296487638f * src[6]); + float mx07 = (-0.275899379282943f * src[1]) + (1.38703984532215f * src[7]); float mx09 = mx00 + mx04; float mx0a = mx01 + mx03; float mx0b = 1.4142135623731f * mx02; @@ -41,29 +43,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils float mx14 = 0.353553390593274f * (mx11 + mx12); float mx15 = 0.353553390593274f * (mx11 - mx12); float mx16 = 0.5f * mx13; - dst[0] = 0.25f * (mx09 + mx0b) + 0.353553390593274f * mx0a; + dst[0] = (0.25f * (mx09 + mx0b)) + (0.353553390593274f * mx0a); dst[1] = 0.707106781186547f * (mx0f + mx15); dst[2] = 0.707106781186547f * (mx0f - mx15); dst[3] = 0.707106781186547f * (mx0e + mx16); dst[4] = 0.707106781186547f * (mx0e - mx16); dst[5] = 0.707106781186547f * (mx10 - mx14); dst[6] = 0.707106781186547f * (mx10 + mx14); - dst[7] = 0.25f * (mx09 + mx0b) - 0.353553390593274f * mx0a; + dst[7] = (0.25f * (mx09 + mx0b)) - (0.353553390593274f * mx0a); dst = dst.Slice(8); src = src.Slice(8); } } - public static void iDCT8x8GT(Span s, Span d) + public static void IDCT8x8GT(Span s, Span d) { - idct81d_GT(s, d); + Idct81d_GT(s, d); Transpose8x8(d); - idct81d_GT(d, d); + Idct81d_GT(d, d); Transpose8x8(d); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs index 0c644e5c21..533ecaca1a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -1,4 +1,6 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -7,6 +9,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -29,12 +32,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { public static Block8x8F TransformIDCT(ref Block8x8F source) { - var s = new float[64]; - source.CopyTo(s); - var d = new float[64]; - var temp = new float[64]; + float[] s = new float[64]; + source.ScaledCopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; - iDCT2D_llm(s, d, temp); + IDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return result; @@ -42,12 +45,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) { - var s = new float[64]; - source.CopyTo(s); - var d = new float[64]; - var temp = new float[64]; + float[] s = new float[64]; + source.ScaledCopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; - fDCT2D_llm(s, d, temp); + FDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return result; @@ -61,12 +64,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static float[] PrintConstants(ITestOutputHelper output) { - var r = new float[8]; + float[] r = new float[8]; for (int i = 0; i < 8; i++) { r[i] = (float)(Cos(i / 16.0 * M_PI) * M_SQRT2); output?.WriteLine($"float r{i} = {r[i]:R}f;"); } + return r; } @@ -75,15 +79,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 /// - /// - /// - private static void iDCT1Dllm_32f(Span y, Span x) + private static void IDCT1Dllm_32f(Span y, Span x) { float a0, a1, a2, a3, b0, b1, b2, b3; float z0, z1, z2, z3, z4; // see: PrintConstants() - float r0 = 1.41421354f; float r1 = 1.3870399f; float r2 = 1.306563f; @@ -101,19 +102,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils z0 = z0 * (-r3 + r7); z1 = z1 * (-r3 - r1); - z2 = z2 * (-r3 - r5) + z4; - z3 = z3 * (-r3 + r5) + z4; + z2 = (z2 * (-r3 - r5)) + z4; + z3 = (z3 * (-r3 + r5)) + z4; - b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; - b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; - b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; - b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; + b3 = (y[7] * (-r1 + r3 + r5 - r7)) + z0 + z2; + b2 = (y[5] * (r1 + r3 - r5 + r7)) + z1 + z3; + b1 = (y[3] * (r1 + r3 + r5 - r7)) + z1 + z2; + b0 = (y[1] * (r1 + r3 - r5 - r7)) + z0 + z3; z4 = (y[2] + y[6]) * r6; z0 = y[0] + y[4]; z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r2 + r6); - z3 = z4 + y[2] * (r2 - r6); + z2 = z4 - (y[6] * (r2 + r6)); + z3 = z4 + (y[2] * (r2 - r6)); a0 = z0 + z3; a3 = z0 - z3; a1 = z1 + z2; @@ -133,23 +134,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 /// Applies IDCT transformation on "s" copying transformed values to "d", using temporary block "temp" /// - /// - /// - /// - internal static void iDCT2D_llm(Span s, Span d, Span temp) + internal static void IDCT2D_llm(Span s, Span d, Span temp) { int j; for (j = 0; j < 8; j++) { - iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + IDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); for (j = 0; j < 8; j++) { - iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + IDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); @@ -168,27 +166,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Source /// Destination - public static void fDCT2D8x4_32f(Span s, Span d) + public static void FDCT2D8x4_32f(Span s, Span d) { Vector4 c0 = _mm_load_ps(s, 0); Vector4 c1 = _mm_load_ps(s, 56); - Vector4 t0 = (c0 + c1); - Vector4 t7 = (c0 - c1); + Vector4 t0 = c0 + c1; + Vector4 t7 = c0 - c1; c1 = _mm_load_ps(s, 48); c0 = _mm_load_ps(s, 8); - Vector4 t1 = (c0 + c1); - Vector4 t6 = (c0 - c1); + Vector4 t1 = c0 + c1; + Vector4 t6 = c0 - c1; c1 = _mm_load_ps(s, 40); c0 = _mm_load_ps(s, 16); - Vector4 t2 = (c0 + c1); - Vector4 t5 = (c0 - c1); + Vector4 t2 = c0 + c1; + Vector4 t5 = c0 - c1; c0 = _mm_load_ps(s, 24); c1 = _mm_load_ps(s, 32); - Vector4 t3 = (c0 + c1); - Vector4 t4 = (c0 - c1); + Vector4 t3 = c0 + c1; + Vector4 t4 = c0 - c1; /* c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; @@ -197,19 +195,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; */ - c0 = (t0 + t3); - Vector4 c3 = (t0 - t3); - c1 = (t1 + t2); - Vector4 c2 = (t1 - t2); + c0 = t0 + t3; + Vector4 c3 = t0 - t3; + c1 = t1 + t2; + Vector4 c2 = t1 - t2; /* c0 = t0 + t3; c3 = t0 - t3; c1 = t1 + t2; c2 = t1 - t2; */ - _mm_store_ps(d, 0, (c0 + c1)); + _mm_store_ps(d, 0, c0 + c1); - _mm_store_ps(d, 32, (c0 - c1)); + _mm_store_ps(d, 32, c0 - c1); /*y[0] = c0 + c1; y[4] = c0 - c1;*/ @@ -217,9 +215,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils var w0 = new Vector4(0.541196f); var w1 = new Vector4(1.306563f); - _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); + _mm_store_ps(d, 16, (w0 * c2) + (w1 * c3)); - _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + _mm_store_ps(d, 48, (w0 * c3) - (w1 * c2)); /* y[2] = c2 * r[6] + c3 * r[2]; y[6] = c3 * r[6] - c2 * r[2]; @@ -227,8 +225,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils w0 = new Vector4(1.175876f); w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); + c3 = (w0 * t4) + (w1 * t7); + c0 = (w0 * t7) - (w1 * t4); /* c3 = t4 * r[3] + t7 * r[5]; c0 = t7 * r[3] - t4 * r[5]; @@ -236,78 +234,83 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils w0 = new Vector4(1.387040f); w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); + c2 = (w0 * t5) + (w1 * t6); + c1 = (w0 * t6) - (w1 * t5); /* c2 = t5 * r[1] + t6 * r[7]; c1 = t6 * r[1] - t5 * r[7]; */ - _mm_store_ps(d, 24, (c0 - c2)); + _mm_store_ps(d, 24, c0 - c2); - _mm_store_ps(d, 40, (c3 - c1)); - //y[5] = c3 - c1; y[3] = c0 - c2; + _mm_store_ps(d, 40, c3 - c1); + // y[5] = c3 - c1; y[3] = c0 - c2; var invsqrt2 = new Vector4(0.707107f); - c0 = ((c0 + c2) * invsqrt2); - c3 = ((c3 + c1) * invsqrt2); - //c0 = (c0 + c2) * invsqrt2; - //c3 = (c3 + c1) * invsqrt2; - - _mm_store_ps(d, 8, (c0 + c3)); + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; - _mm_store_ps(d, 56, (c0 - c3)); - //y[1] = c0 + c3; y[7] = c0 - c3; + // c0 = (c0 + c2) * invsqrt2; + // c3 = (c3 + c1) * invsqrt2; + _mm_store_ps(d, 8, c0 + c3); + _mm_store_ps(d, 56, c0 - c3); + // y[1] = c0 + c3; y[7] = c0 - c3; /*for(i = 0;i < 8;i++) { y[i] *= invsqrt2h; }*/ } - public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) + public static void FDCT8x8_llm_sse(Span s, Span d, Span temp) { Transpose8x8(s, temp); - fDCT2D8x4_32f(temp, d); + FDCT2D8x4_32f(temp, d); - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); Transpose8x8(d, temp); - fDCT2D8x4_32f(temp, d); + FDCT2D8x4_32f(temp, d); - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); var c = new Vector4(0.1250f); - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//0 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//1 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//2 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//3 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//4 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//5 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//6 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//7 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//8 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//9 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//10 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//11 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//12 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//13 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//14 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//15 +#pragma warning disable SA1107 // Code should not contain multiple statements on one line + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 0 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 1 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 2 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 3 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 4 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 5 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 6 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 7 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 8 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 9 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 10 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 11 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 12 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 13 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 14 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 15 +#pragma warning restore SA1107 // Code should not contain multiple statements on one line } [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1300 // Element should begin with upper-case letter private static Vector4 _mm_load_ps(Span src, int offset) +#pragma warning restore SA1300 // Element should begin with upper-case letter { src = src.Slice(offset); return new Vector4(src[0], src[1], src[2], src[3]); } [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1300 // Element should begin with upper-case letter private static void _mm_store_ps(Span dest, int offset, Vector4 src) +#pragma warning restore SA1300 // Element should begin with upper-case letter { dest = dest.Slice(offset); dest[0] = src.X; @@ -318,7 +321,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // Accurate variants of constants from: // https://github.com/mozilla/mozjpeg/blob/master/simd/jfdctint-altivec.c - +#pragma warning disable SA1309 // Field names should not begin with underscore private static readonly Vector4 _1_175876 = new Vector4(1.175875602f); private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); @@ -342,15 +345,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); private static readonly Vector4 _0_765367 = new Vector4(0.765366865f); +#pragma warning restore SA1309 // Field names should not begin with underscore /// /// Original: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 /// Does a part of the IDCT job on the given parts of the blocks /// - /// - /// - internal static void iDCT2D8x4_32f(Span y, Span x) + internal static void IDCT2D8x4_32f(Span y, Span x) { /* float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; @@ -377,12 +379,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Vector4 mz1 = my3 + my5; Vector4 mz3 = my1 + my5; - Vector4 mz4 = ((mz0 + mz1) * _1_175876); - //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; - //z4 = (z0 + z1) * r[3]; + Vector4 mz4 = (mz0 + mz1) * _1_175876; - mz2 = mz2 * _1_961571 + mz4; - mz3 = mz3 * _0_390181 + mz4; + // z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; + // z4 = (z0 + z1) * r[3]; + mz2 = (mz2 * _1_961571) + mz4; + mz3 = (mz3 * _0_390181) + mz4; mz0 = mz0 * _0_899976; mz1 = mz1 * _2_562915; @@ -396,10 +398,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils z2 = z2 * (-r[3] - r[5]) + z4; z3 = z3 * (-r[3] + r[5]) + z4;*/ - Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; - Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; - Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; - Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; + Vector4 mb3 = (my7 * _0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * _2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * _3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * _1_501321) + mz0 + mz3; /* 0.298631 @@ -420,8 +422,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils mz0 = my0 + my4; mz1 = my0 - my4; - mz2 = mz4 + my6 * _1_847759; - mz3 = mz4 + my2 * _0_765367; + mz2 = mz4 + (my6 * _1_847759); + mz3 = mz4 + (my2 * _0_765367); my0 = mz0 + mz3; my3 = mz0 - mz3; @@ -462,13 +464,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils */ } - internal static void fDCT1Dllm_32f(Span x, Span y) + internal static void FDCT1Dllm_32f(Span x, Span y) { float t0, t1, t2, t3, t4, t5, t6, t7; float c0, c1, c2, c3; - var r = new float[8]; + float[] r = new float[8]; - //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + // for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } r[0] = 1.414214f; r[1] = 1.387040f; r[2] = 1.306563f; @@ -478,9 +480,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils r[6] = 0.541196f; r[7] = 0.275899f; - const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); - //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; + const float invsqrt2 = 0.707107f; // (float)(1.0f / M_SQRT2); + // const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; c1 = x[0]; c2 = x[7]; t0 = c1 + c2; @@ -505,13 +507,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y[0] = c0 + c1; y[4] = c0 - c1; - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; + y[2] = (c2 * r[6]) + (c3 * r[2]); + y[6] = (c3 * r[6]) - (c2 * r[2]); - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; + c3 = (t4 * r[3]) + (t7 * r[5]); + c0 = (t7 * r[3]) - (t4 * r[5]); + c2 = (t5 * r[1]) + (t6 * r[7]); + c1 = (t6 * r[1]) - (t5 * r[7]); y[5] = c3 - c1; y[3] = c0 - c2; @@ -521,7 +523,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y[7] = c0 - c3; } - internal static void fDCT2D_llm( + internal static void FDCT2D_llm( Span s, Span d, Span temp, @@ -532,14 +534,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int j = 0; j < 8; j++) { - fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + FDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); for (int j = 0; j < 8; j++) { - fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + FDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs index a929e0eb02..c11edb67c7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -1,8 +1,11 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -40,18 +43,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// public static class StandardIntegerDCT { - private const int fix_0_298631336 = 2446; - private const int fix_0_390180644 = 3196; - private const int fix_0_541196100 = 4433; - private const int fix_0_765366865 = 6270; - private const int fix_0_899976223 = 7373; - private const int fix_1_175875602 = 9633; - private const int fix_1_501321110 = 12299; - private const int fix_1_847759065 = 15137; - private const int fix_1_961570560 = 16069; - private const int fix_2_053119869 = 16819; - private const int fix_2_562915447 = 20995; - private const int fix_3_072711026 = 25172; + private const int Fix_0_298631336 = 2446; + private const int Fix_0_390180644 = 3196; + private const int Fix_0_541196100 = 4433; + private const int Fix_0_765366865 = 6270; + private const int Fix_0_899976223 = 7373; + private const int Fix_1_175875602 = 9633; + private const int Fix_1_501321110 = 12299; + private const int Fix_1_847759065 = 15137; + private const int Fix_1_961570560 = 16069; + private const int Fix_2_053119869 = 16819; + private const int Fix_2_562915447 = 20995; + private const int Fix_3_072711026 = 25172; /// /// The number of bits @@ -127,25 +130,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; + int z1 = (tmp12 + tmp13) * Fix_0_541196100; z1 += 1 << (Bits - Pass1Bits - 1); - block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); - block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + block[y8 + 2] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits - Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 = (tmp12 + tmp13) * Fix_1_175875602; z1 += 1 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; + tmp0 = tmp0 * Fix_1_501321110; + tmp1 = tmp1 * Fix_3_072711026; + tmp2 = tmp2 * Fix_2_053119869; + tmp3 = tmp3 * Fix_0_298631336; + tmp10 = tmp10 * -Fix_0_899976223; + tmp11 = tmp11 * -Fix_2_562915447; + tmp12 = tmp12 * -Fix_0_390180644; + tmp13 = tmp13 * -Fix_1_961570560; tmp12 += z1; tmp13 += z1; @@ -177,25 +180,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[x] = (tmp10 + tmp11) >> Pass1Bits; block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; + int z1 = (tmp12 + tmp13) * Fix_0_541196100; z1 += 1 << (Bits + Pass1Bits - 1); - block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); - block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + block[16 + x] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits + Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 = (tmp12 + tmp13) * Fix_1_175875602; z1 += 1 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; + tmp0 = tmp0 * Fix_1_501321110; + tmp1 = tmp1 * Fix_3_072711026; + tmp2 = tmp2 * Fix_2_053119869; + tmp3 = tmp3 * Fix_0_298631336; + tmp10 = tmp10 * -Fix_0_899976223; + tmp11 = tmp11 * -Fix_2_562915447; + tmp12 = tmp12 * -Fix_0_390180644; + tmp13 = tmp13 * -Fix_1_961570560; tmp12 += z1; tmp13 += z1; @@ -204,23 +207,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); } - } - private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int w1pw7 = w1 + w7; - private const int w1mw7 = w1 - w7; - private const int w2pw6 = w2 + w6; - private const int w2mw6 = w2 - w6; - private const int w3pw5 = w3 + w5; - private const int w3mw5 = w3 - w5; - - private const int r2 = 181; // 256/sqrt(2) + + private const int W1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int W2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int W3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int W5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int W6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int W7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int W1pw7 = W1 + W7; + private const int W1mw7 = W1 - W7; + private const int W2pw6 = W2 + W6; + private const int W2mw6 = W2 - W6; + private const int W3pw5 = W3 + W5; + private const int W3mw5 = W3 - W5; + + private const int R2 = 181; // 256/sqrt(2) /// /// Performs a 2-D Inverse Discrete Cosine Transformation. @@ -235,7 +238,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. /// /// The source block of coefficients - // [Obsolete("Looks like this method produces really bad results for bigger values!")] public static void TransformIDCTInplace(Span src) { // Horizontal 1-D IDCT. @@ -270,19 +272,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int x7 = src[y8 + 3]; // Stage 1. - int x8 = w7 * (x4 + x5); - x4 = x8 + (w1mw7 * x4); - x5 = x8 - (w1pw7 * x5); - x8 = w3 * (x6 + x7); - x6 = x8 - (w3mw5 * x6); - x7 = x8 - (w3pw5 * x7); + int x8 = W7 * (x4 + x5); + x4 = x8 + (W1mw7 * x4); + x5 = x8 - (W1pw7 * x5); + x8 = W3 * (x6 + x7); + x6 = x8 - (W3mw5 * x6); + x7 = x8 - (W3pw5 * x7); // Stage 2. x8 = x0 + x1; x0 -= x1; - x1 = w6 * (x3 + x2); - x2 = x1 - (w2pw6 * x2); - x3 = x1 + (w2mw6 * x3); + x1 = W6 * (x3 + x2); + x2 = x1 - (W2pw6 * x2); + x3 = x1 + (W2mw6 * x3); x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; @@ -293,8 +295,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils x8 -= x3; x3 = x0 + x2; x0 -= x2; - x2 = ((r2 * (x4 + x5)) + 128) >> 8; - x4 = ((r2 * (x4 - x5)) + 128) >> 8; + x2 = ((R2 * (x4 + x5)) + 128) >> 8; + x4 = ((R2 * (x4 - x5)) + 128) >> 8; // Stage 4. src[y8 + 0] = (x7 + x1) >> 8; @@ -325,19 +327,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int y7 = src[24 + x]; // Stage 1. - int y8 = (w7 * (y4 + y5)) + 4; - y4 = (y8 + (w1mw7 * y4)) >> 3; - y5 = (y8 - (w1pw7 * y5)) >> 3; - y8 = (w3 * (y6 + y7)) + 4; - y6 = (y8 - (w3mw5 * y6)) >> 3; - y7 = (y8 - (w3pw5 * y7)) >> 3; + int y8 = (W7 * (y4 + y5)) + 4; + y4 = (y8 + (W1mw7 * y4)) >> 3; + y5 = (y8 - (W1pw7 * y5)) >> 3; + y8 = (W3 * (y6 + y7)) + 4; + y6 = (y8 - (W3mw5 * y6)) >> 3; + y7 = (y8 - (W3pw5 * y7)) >> 3; // Stage 2. y8 = y0 + y1; y0 -= y1; - y1 = (w6 * (y3 + y2)) + 4; - y2 = (y1 - (w2pw6 * y2)) >> 3; - y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = (W6 * (y3 + y2)) + 4; + y2 = (y1 - (W2pw6 * y2)) >> 3; + y3 = (y1 + (W2mw6 * y3)) >> 3; y1 = y4 + y6; y4 -= y6; y6 = y5 + y7; @@ -348,8 +350,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y8 -= y3; y3 = y0 + y2; y0 -= y2; - y2 = ((r2 * (y4 + y5)) + 128) >> 8; - y4 = ((r2 * (y4 - y5)) + 128) >> 8; + y2 = ((R2 * (y4 + y5)) + 128) >> 8; + y4 = ((R2 * (y4 - y5)) + 128) >> 8; // Stage 4. src[x] = (y7 + y1) >> 14; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index 527cc3feda..4de576b256 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -1,13 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { /// @@ -34,7 +33,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Transpose 8x8 block stored linearly in a (inplace) /// - /// internal static void Transpose8x8(Span data) { for (int i = 1; i < 8; i++) @@ -43,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int j = 0; j < i; j++) { float tmp = data[i8 + j]; - data[i8 + j] = data[j * 8 + i]; - data[j * 8 + i] = tmp; + data[i8 + j] = data[(j * 8) + i]; + data[(j * 8) + i] = tmp; } } } @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int i8 = i * 8; for (int j = 0; j < 8; j++) { - dest[j * 8 + i] = src[i8 + j]; + dest[(j * 8) + i] = src[i8 + j]; } } } @@ -67,9 +65,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Copies color values from block to the destination image buffer. /// - /// - /// - /// internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) { fixed (Block8x8F* p = &block) @@ -128,11 +123,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } /// - /// Rounds a rational number defined as dividend/divisor into an integer + /// Rounds a rational number defined as dividend/divisor into an integer. /// - /// The dividend - /// The divisor - /// + /// The dividend. + /// The divisor. + /// The rounded value. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int RationalRound(int dividend, int divisor) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs index 296f424fa5..13685c8e8c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -1,9 +1,11 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -49,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils TestImageProvider provider, LibJpegTools.SpectralData data, ITestOutputHelper output = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (LibJpegTools.ComponentData comp in data.Components) { @@ -72,4 +74,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 64a394cc98..3a207722bd 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs @@ -1,4 +1,7 @@ -using System.Buffers.Binary; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers.Binary; using System.Text; using SixLabors.ImageSharp.Formats.Png; using Xunit; @@ -10,15 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Fact] public void ChunkTypeIdsAreCorrect() { - Assert.Equal(PngChunkType.Header, GetType("IHDR")); - Assert.Equal(PngChunkType.Palette, GetType("PLTE")); - Assert.Equal(PngChunkType.Data, GetType("IDAT")); - Assert.Equal(PngChunkType.End, GetType("IEND")); + Assert.Equal(PngChunkType.Header, GetType("IHDR")); + Assert.Equal(PngChunkType.Palette, GetType("PLTE")); + Assert.Equal(PngChunkType.Data, GetType("IDAT")); + Assert.Equal(PngChunkType.End, GetType("IEND")); Assert.Equal(PngChunkType.Transparency, GetType("tRNS")); - Assert.Equal(PngChunkType.Text, GetType("tEXt")); - Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); - Assert.Equal(PngChunkType.Physical, GetType("pHYs")); - Assert.Equal(PngChunkType.Exif, GetType("eXIf")); + Assert.Equal(PngChunkType.Text, GetType("tEXt")); + Assert.Equal(PngChunkType.InternationalText, GetType("iTXt")); + Assert.Equal(PngChunkType.CompressedText, GetType("zTXt")); + Assert.Equal(PngChunkType.Chroma, GetType("cHRM")); + Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); + Assert.Equal(PngChunkType.Physical, GetType("pHYs")); + Assert.Equal(PngChunkType.Exif, GetType("eXIf")); + Assert.Equal(PngChunkType.Time, GetType("tIME")); + Assert.Equal(PngChunkType.Background, GetType("bKGD")); + Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP")); + Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB")); + Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT")); + Assert.Equal(PngChunkType.Histogram, GetType("hIST")); + Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT")); + Assert.Equal(PngChunkType.ProprietaryApple, GetType("CgBI")); } private static PngChunkType GetType(string text) @@ -26,4 +40,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index e976d5a768..6cefff95d4 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -1,13 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Buffers.Binary; using System.IO; using System.Text; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.PixelFormats; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { public partial class PngDecoderTests @@ -15,19 +19,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. private static readonly byte[] Raw1X1PngIhdrAndpHYs = { - // PNG Identifier + // PNG Identifier 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // IHDR 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, + // IHDR CRC 0x90, 0x77, 0x53, 0xDE, // pHYS 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, + // pHYS CRC 0xC7, 0x6F, 0xA8, 0x64 }; @@ -53,8 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [InlineData((uint)PngChunkType.Header)] // IHDR [InlineData((uint)PngChunkType.Palette)] // PLTE - // [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this - [InlineData((uint)PngChunkType.End)] // IEND + /* [InlineData(PngChunkTypes.Data)] TODO: Figure out how to test this */ public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) { string chunkName = GetChunkTypeName(chunkType); @@ -68,30 +73,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); ImageFormatException exception = - Assert.Throws(() => decoder.Decode(null, memStream)); + Assert.Throws(() => decoder.Decode(null, memStream)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } } - [Theory] - [InlineData((uint)PngChunkType.Gamma)] // gAMA - [InlineData((uint)PngChunkType.Transparency)] // tRNS - [InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks. - //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this - public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType) + [Fact] + public void CalculateCrc_Works() { - string chunkName = GetChunkTypeName(chunkType); + // arrange + var data = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; + var crc = new Crc32(); - using (var memStream = new MemoryStream()) - { - WriteHeaderChunk(memStream); - WriteChunk(memStream, chunkName); - WriteDataChunk(memStream); + // act + crc.Update(data); - var decoder = new PngDecoder(); - decoder.Decode(null, memStream); - } + // assert + Assert.Equal(0x88AA689F, crc.Value); } private static string GetChunkTypeName(uint value) @@ -105,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteHeaderChunk(MemoryStream memStream) { - // Writes a 1x1 32bit png header chunk containing a single black pixel + // Writes a 1x1 32bit png header chunk containing a single black pixel. memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); } @@ -120,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteDataChunk(MemoryStream memStream) { - // Writes a 1x1 32bit png data chunk containing a single black pixel + // Writes a 1x1 32bit png data chunk containing a single black pixel. memStream.Write(Raw1X1PngIdatAndIend, 0, Raw1X1PngIdatAndIend.Length); memStream.Position = 0; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 91b1ef2c17..72b27ec5d6 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,86 +1,85 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System.IO; +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { public partial class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private static PngDecoder PngDecoder => new PngDecoder(); + public static readonly string[] CommonTestImages = { TestImages.Png.Splash, - TestImages.Png.Indexed, TestImages.Png.FilterVar, - TestImages.Png.Bad.ChunkLength1, - TestImages.Png.Bad.CorruptedChunk, TestImages.Png.VimImage1, + TestImages.Png.VimImage2, TestImages.Png.VersioningImage1, TestImages.Png.VersioningImage2, TestImages.Png.SnakeGame, - TestImages.Png.Banner7Adam7InterlaceMode, - TestImages.Png.Banner8Index, - - TestImages.Png.Bad.ChunkLength2, - TestImages.Png.VimImage2, TestImages.Png.Rgb24BppTrans, - TestImages.Png.GrayAlpha8Bit, - TestImages.Png.Gray1BitTrans, - TestImages.Png.Bad.ZlibOverflow, - TestImages.Png.Bad.ZlibOverflow2, - TestImages.Png.Bad.ZlibZtxtBadHeader, - }; - public static readonly string[] TestImages48Bpp = - { - TestImages.Png.Rgb48Bpp, - TestImages.Png.Rgb48BppInterlaced + TestImages.Png.Bad.ChunkLength1, + TestImages.Png.Bad.ChunkLength2, }; - public static readonly string[] TestImages64Bpp = -{ - TestImages.Png.Rgba64Bpp, - TestImages.Png.Rgb48BppTrans + public static readonly string[] TestImagesIssue1014 = + { + TestImages.Png.Issue1014_1, TestImages.Png.Issue1014_2, + TestImages.Png.Issue1014_3, TestImages.Png.Issue1014_4, + TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6 }; - public static readonly string[] TestImagesGray16Bit = + public static readonly string[] TestImagesIssue1177 = { - TestImages.Png.Gray16Bit, + TestImages.Png.Issue1177_1, + TestImages.Png.Issue1177_2 }; - public static readonly string[] TestImagesGrayAlpha16Bit = + public static readonly string[] CorruptedTestImages = { - TestImages.Png.GrayAlpha16Bit, - TestImages.Png.GrayTrns16BitInterlaced + TestImages.Png.Bad.CorruptedChunk, + TestImages.Png.Bad.ZlibOverflow, + TestImages.Png.Bad.ZlibOverflow2, + TestImages.Png.Bad.ZlibZtxtBadHeader, }; - public static readonly string[] TestImagesGray8BitInterlaced = - { - TestImages.Png.GrayAlpha1BitInterlaced, - TestImages.Png.GrayAlpha2BitInterlaced, - TestImages.Png.Gray4BitInterlaced, - TestImages.Png.GrayAlpha8BitInterlaced - }; - [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } + + [Theory] + [WithFile(TestImages.Png.GrayA8Bit, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Gray1BitTrans, PixelTypes.Rgba32)] + public void Decode_GrayWithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -89,10 +88,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] - public void Decode_Interlaced_ImageIsCorrect(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.Banner7Adam7InterlaceMode, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] + public void Decode_Interlaced(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } + + [Theory] + [WithFile(TestImages.Png.Indexed, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedFourColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedSixteenColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] + public void Decode_Indexed(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -100,11 +118,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgb48)] + [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgb48)] + [WithFile(TestImages.Png.Rgb48BppInterlaced, PixelTypes.Rgb48)] public void Decode_48Bpp(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -112,11 +131,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImages64Bpp), PixelTypes.Rgba64)] + [WithFile(TestImages.Png.Rgba64Bpp, PixelTypes.Rgba64)] + [WithFile(TestImages.Png.Rgb48BppTrans, PixelTypes.Rgba64)] public void Decode_64Bpp(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -124,11 +144,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImagesGray8BitInterlaced), PixelTypes.Rgba32)] - public void Decoder_Gray8bitInterlaced(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.GrayAlpha1BitInterlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Gray4BitInterlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes.Rgba32)] + public void Decoder_L8bitInterlaced(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -136,11 +159,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImagesGray16Bit), PixelTypes.Rgb48)] - public void Decode_Gray16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(TestImages.Png.L16Bit, PixelTypes.Rgb48)] + public void Decode_L16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -148,11 +171,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFileCollection(nameof(TestImagesGrayAlpha16Bit), PixelTypes.Rgba64)] + [WithFile(TestImages.Png.GrayAlpha16Bit, PixelTypes.Rgba64)] + [WithFile(TestImages.Png.GrayTrns16BitInterlaced, PixelTypes.Rgba64)] public void Decode_GrayAlpha16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } + + [Theory] + [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes)] + public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -160,11 +196,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithFile(TestImages.Png.GrayAlpha8BitInterlaced, PixelTypes)] - public void Decoder_CanDecodeGrey8bitWithAlpha(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(CorruptedTestImages), PixelTypes.Rgba32)] + public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -174,9 +210,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.Splash, PixelTypes)] public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -199,5 +235,195 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } } + + [Theory] + [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] + public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + } + }); + Assert.NotNull(ex); + Assert.Contains("PNG Image does not contain a data chunk", ex.Message); + } + + [Theory] + [WithFile(TestImages.Png.Bad.BitDepthZero, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bad.BitDepthThree, PixelTypes.Rgba32)] + public void Decode_InvalidBitDepth_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + } + }); + Assert.NotNull(ex); + Assert.Contains("Invalid or unsupported bit depth", ex.Message); + } + + [Theory] + [WithFile(TestImages.Png.Bad.ColorTypeOne, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bad.ColorTypeNine, PixelTypes.Rgba32)] + public void Decode_InvalidColorType_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + } + }); + Assert.NotNull(ex); + Assert.Contains("Invalid or unsupported color type", ex.Message); + } + + // https://github.com/SixLabors/ImageSharp/issues/1014 + [Theory] + [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] + public void Issue1014_DataSplitOverMultipleIDatChunks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + }); + Assert.Null(ex); + } + + // https://github.com/SixLabors/ImageSharp/issues/1177 + [Theory] + [WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)] + public void Issue1177_CRC_Omitted(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + }); + Assert.Null(ex); + } + + // https://github.com/SixLabors/ImageSharp/issues/1127 + [Theory] + [WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)] + public void Issue1127(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + }); + Assert.Null(ex); + } + + // https://github.com/SixLabors/ImageSharp/issues/1047 + [Theory] + [WithFile(TestImages.Png.Bad.Issue1047_BadEndChunk, PixelTypes.Rgba32)] + public void Issue1047(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); + } + } + }); + Assert.Null(ex); + } + + // https://github.com/SixLabors/ImageSharp/issues/410 + [Theory] + [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] + public void Issue410_MalformedApplePng(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); + } + } + }); + Assert.NotNull(ex); + Assert.Contains("Proprietary Apple PNG detected!", ex.Message); + } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(PngDecoder)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index c72dbe2891..ce734f6cfb 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming +using System; +using System.Buffers.Binary; using System.IO; using System.Linq; @@ -18,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { public class PngEncoderTests { + private static PngEncoder PngEncoder => new PngEncoder(); + public static readonly TheoryData PngBitDepthFiles = new TheoryData { @@ -31,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, { TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale }, - { TestImages.Png.Gray8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale }, + { TestImages.Png.L8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale }, { TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale }, { TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb }, { TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb } @@ -63,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// public static readonly TheoryData CompressionLevels = new TheoryData { - 1, 2, 3, 4, 5, 6, 7, 8, 9 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; public static readonly TheoryData PaletteSizes = new TheoryData @@ -85,8 +89,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, - { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; @@ -98,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TestPngEncoderCore( provider, @@ -112,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -130,7 +134,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -147,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -179,8 +183,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] public void WorksWithAllBitDepths(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. + if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) + { + return; + } + foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( @@ -212,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] public void WorksWithAllBitDepthsOptimized(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -229,11 +239,64 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Theory] + [WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgr565, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgra4444, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Byte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfSingle, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfVector2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfVector4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedByte2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedByte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedShort4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rg32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgba1010102, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.RgbaVector, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Short2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Short4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgb24, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgr24, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgra32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgb48, PngColorType.Rgb, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Bgra5551, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.L8, PngColorType.Grayscale, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.L16, PngColorType.Grayscale, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.La16, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.La32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] + public void InfersColorTypeAndBitDepth(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) + where TPixel : unmanaged, IPixel + { + using (Stream stream = new MemoryStream()) + { + PngEncoder.Encode(provider.GetImage(), stream); + + stream.Seek(0, SeekOrigin.Begin); + + var decoder = new PngDecoder(); + + Image image = decoder.Decode(Configuration.Default, stream); + + PngMetadata metadata = image.Metadata.GetPngMetadata(); + Assert.Equal(pngColorType, metadata.ColorType); + Assert.Equal(pngBitDepth, metadata.BitDepth); + } + } + [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. + if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) + { + return; + } + foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( @@ -250,15 +313,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var ms = new MemoryStream()) { - image.Save(ms, new PngEncoder()); + image.Save(ms, PngEncoder); byte[] data = ms.ToArray().Take(8).ToArray(); - byte[] expected = { + byte[] expected = + { 0x89, // Set the high bit. 0x50, // P 0x4E, // N @@ -277,14 +341,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [MemberData(nameof(RatioFiles))] public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - var options = new PngEncoder(); - var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, PngEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) @@ -302,19 +364,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [MemberData(nameof(PngBitDepthFiles))] public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) { - var options = new PngEncoder(); - var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, PngEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) { - PngMetadata meta = output.Metadata.GetFormatMetadata(PngFormat.Instance); + PngMetadata meta = output.Metadata.GetPngMetadata(); Assert.Equal(pngBitDepth, meta.BitDepth); } @@ -326,21 +386,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [MemberData(nameof(PngTrnsFiles))] public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) { - var options = new PngEncoder(); - var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { - PngMetadata inMeta = input.Metadata.GetFormatMetadata(PngFormat.Instance); + PngMetadata inMeta = input.Metadata.GetPngMetadata(); Assert.True(inMeta.HasTransparency); using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, PngEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) { - PngMetadata outMeta = output.Metadata.GetFormatMetadata(PngFormat.Instance); + PngMetadata outMeta = output.Metadata.GetPngMetadata(); Assert.True(outMeta.HasTransparency); switch (pngColorType) @@ -348,13 +406,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png case PngColorType.Grayscale: if (pngBitDepth.Equals(PngBitDepth.Bit16)) { - Assert.True(outMeta.TransparentGray16.HasValue); - Assert.Equal(inMeta.TransparentGray16, outMeta.TransparentGray16); + Assert.True(outMeta.TransparentL16.HasValue); + Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16); } else { - Assert.True(outMeta.TransparentGray8.HasValue); - Assert.Equal(inMeta.TransparentGray8, outMeta.TransparentGray8); + Assert.True(outMeta.TransparentL8.HasValue); + Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8); } break; @@ -377,6 +435,146 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Fact] + public void HeaderChunk_ComesFirst() + { + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + memStream.Position = 0; + + // Skip header. + Span bytesSpan = memStream.ToArray().AsSpan(8); + BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.Equal(PngChunkType.Header, type); + } + + [Fact] + public void EndChunk_IsLast() + { + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + memStream.Position = 0; + + // Skip header. + Span bytesSpan = memStream.ToArray().AsSpan(8); + + bool endChunkFound = false; + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.False(endChunkFound); + if (type == PngChunkType.End) + { + endChunkFound = true; + } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } + } + + [Theory] + [InlineData(PngChunkType.Gamma)] + [InlineData(PngChunkType.Chroma)] + [InlineData(PngChunkType.EmbeddedColorProfile)] + [InlineData(PngChunkType.SignificantBits)] + [InlineData(PngChunkType.StandardRgbColourSpace)] + public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj) + { + var chunkType = (PngChunkType)chunkTypeObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + memStream.Position = 0; + + // Skip header. + Span bytesSpan = memStream.ToArray().AsSpan(8); + + bool palFound = false; + bool dataFound = false; + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + if (chunkType == type) + { + Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk"); + } + + switch (type) + { + case PngChunkType.Data: + dataFound = true; + break; + case PngChunkType.Palette: + palFound = true; + break; + } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } + } + + [Theory] + [InlineData(PngChunkType.Physical)] + [InlineData(PngChunkType.SuggestedPalette)] + public void Chunk_ComesBeforeIDat(object chunkTypeObj) + { + var chunkType = (PngChunkType)chunkTypeObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + memStream.Position = 0; + + // Skip header. + Span bytesSpan = memStream.ToArray().AsSpan(8); + + bool dataFound = false; + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + if (chunkType == type) + { + Assert.False(dataFound, $"{chunkType} chunk should come before data chunk"); + } + + if (type == PngChunkType.Data) + { + dataFound = true; + } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } + } + + [Theory] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32)] + [WithTestPatternImages(677, 683, PixelTypes.Rgba32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + PngColorType.Rgb, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); + } + } + private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, @@ -392,7 +590,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png bool appendPaletteSize = false, bool appendPngBitDepth = false, PngOptimizeMethod optimizeMethod = PngOptimizeMethod.None) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -402,7 +600,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png FilterMethod = pngFilterMethod, CompressionLevel = compressionLevel, BitDepth = bitDepth, - Quantizer = new WuQuantizer(paletteSize), + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), InterlaceMethod = interlaceMode, OptimizeMethod = optimizeMethod, }; @@ -420,6 +618,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // Compare to the Magick reference decoder. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + // We compare using both our decoder and the reference decoder as pixel transformation // occurs within the encoder itself leaving the input image unaffected. // This means we are benefiting from testing our decoder also. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs deleted file mode 100644 index dfa7fd2922..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Png -{ - public class PngMetaDataTests - { - public static readonly TheoryData RatioFiles = - new TheoryData - { - { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, - { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, - { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - [Fact] - public void CloneIsDeep() - { - var meta = new PngMetadata - { - BitDepth = PngBitDepth.Bit16, - ColorType = PngColorType.GrayscaleWithAlpha, - InterlaceMethod = PngInterlaceMode.Adam7, - Gamma = 2, - TextData = new List { new PngTextData("name", "value", "foo", "bar") } - }; - - var clone = (PngMetadata)meta.DeepClone(); - - clone.BitDepth = PngBitDepth.Bit2; - clone.ColorType = PngColorType.Palette; - clone.InterlaceMethod = PngInterlaceMode.None; - clone.Gamma = 1; - - Assert.False(meta.BitDepth == clone.BitDepth); - Assert.False(meta.ColorType == clone.ColorType); - Assert.False(meta.InterlaceMethod == clone.InterlaceMethod); - Assert.False(meta.Gamma.Equals(clone.Gamma)); - Assert.False(meta.TextData.Equals(clone.TextData)); - Assert.True(meta.TextData.SequenceEqual(clone.TextData)); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetaData, PixelTypes.Rgba32)] - public void Decoder_CanReadTextData(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage(new PngDecoder())) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && m.LanguageTag.Equals("chinese")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort")); - } - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetaData, PixelTypes.Rgba32)] - public void Encoder_PreservesTextData(TestImageProvider provider) - where TPixel : struct, IPixel - { - var decoder = new PngDecoder(); - using (Image input = provider.GetImage(decoder)) - using (var memoryStream = new MemoryStream()) - { - input.Save(memoryStream, new PngEncoder()); - - memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && m.LanguageTag.Equals("chinese")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag")); - Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort")); - } - } - } - - [Theory] - [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)] - public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage(new PngDecoder())) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("leading space")); - Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("trailing space")); - Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("space")); - Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("empty")); - Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("invalid characters")); - Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("too large")); - } - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetaData, PixelTypes.Rgba32)] - public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) - where TPixel : struct, IPixel - { - var decoder = new PngDecoder(); - using (Image input = provider.GetImage(decoder)) - using (var memoryStream = new MemoryStream()) - { - // this will be a zTXt chunk. - var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); - // this will be a iTXt chunk. - var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); - PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); - inputMetadata.TextData.Add(expectedText); - inputMetadata.TextData.Add(expectedTextNoneLatin); - input.Save(memoryStream, new PngEncoder - { - TextCompressionThreshold = 50 - }); - - memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Contains(meta.TextData, m => m.Equals(expectedText)); - Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); - } - } - } - - [Fact] - public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() - { - var options = new PngDecoder - { - IgnoreMetadata = false - }; - - var testFile = TestFile.Create(TestImages.Png.Blur); - - using (Image image = testFile.CreateRgba32Image(options)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - - Assert.Equal(1, meta.TextData.Count); - Assert.Equal("Software", meta.TextData[0].Keyword); - Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); - Assert.Equal(0.4545d, meta.Gamma, precision: 4); - } - } - - [Fact] - public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() - { - var options = new PngDecoder - { - IgnoreMetadata = true - }; - - var testFile = TestFile.Create(TestImages.Png.Blur); - - using (Image image = testFile.CreateRgba32Image(options)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Equal(0, meta.TextData.Count); - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new PngDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new PngDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs new file mode 100644 index 0000000000..bf42066002 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -0,0 +1,295 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class PngMetadataTests + { + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + + [Fact] + public void CloneIsDeep() + { + var meta = new PngMetadata + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.GrayscaleWithAlpha, + InterlaceMethod = PngInterlaceMode.Adam7, + Gamma = 2, + TextData = new List { new PngTextData("name", "value", "foo", "bar") } + }; + + var clone = (PngMetadata)meta.DeepClone(); + + clone.BitDepth = PngBitDepth.Bit2; + clone.ColorType = PngColorType.Palette; + clone.InterlaceMethod = PngInterlaceMode.None; + clone.Gamma = 1; + + Assert.False(meta.BitDepth == clone.BitDepth); + Assert.False(meta.ColorType == clone.ColorType); + Assert.False(meta.InterlaceMethod == clone.InterlaceMethod); + Assert.False(meta.Gamma.Equals(clone.Gamma)); + Assert.False(meta.TextData.Equals(clone.TextData)); + Assert.True(meta.TextData.SequenceEqual(clone.TextData)); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decoder_CanReadTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); + } + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Encoder_PreservesTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder(); + using (Image input = provider.GetImage(decoder)) + using (var memoryStream = new MemoryStream()) + { + input.Save(memoryStream, new PngEncoder()); + + memoryStream.Position = 0; + using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); + } + } + } + + [Theory] + [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)] + public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("leading space")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("trailing space")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("space")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("empty")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("invalid characters")); + Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("too large")); + } + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder(); + using (Image input = provider.GetImage(decoder)) + using (var memoryStream = new MemoryStream()) + { + // This will be a zTXt chunk. + var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); + + // This will be a iTXt chunk. + var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); + PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); + inputMetadata.TextData.Add(expectedText); + inputMetadata.TextData.Add(expectedTextNoneLatin); + input.Save(memoryStream, new PngEncoder + { + TextCompressionThreshold = 50 + }); + + memoryStream.Position = 0; + using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.Contains(meta.TextData, m => m.Equals(expectedText)); + Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); + } + } + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decode_ReadsExifData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder + { + IgnoreMetadata = false + }; + + using (Image image = provider.GetImage(decoder)) + { + Assert.NotNull(image.Metadata.ExifProfile); + ExifProfile exif = image.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); + } + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder + { + IgnoreMetadata = true + }; + + using (Image image = provider.GetImage(decoder)) + { + Assert.Null(image.Metadata.ExifProfile); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() + { + var options = new PngDecoder + { + IgnoreMetadata = false + }; + + var testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateRgba32Image(options)) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + + Assert.Equal(1, meta.TextData.Count); + Assert.Equal("Software", meta.TextData[0].Keyword); + Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); + Assert.Equal(0.4545d, meta.Gamma, precision: 4); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + { + var options = new PngDecoder + { + IgnoreMetadata = true + }; + + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + + using (Image image = testFile.CreateRgba32Image(options)) + { + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.Equal(0, meta.TextData.Count); + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new PngDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new PngDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + + [Theory] + [InlineData(TestImages.Png.PngWithMetadata)] + public void Identify_ReadsTextData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); + } + } + + [Theory] + [InlineData(TestImages.Png.PngWithMetadata)] + public void Identify_ReadsExifData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.Metadata.ExifProfile); + ExifProfile exif = imageInfo.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); + } + } + + private static void VerifyExifDataIsPresent(ExifProfile exif) + { + Assert.Equal(1, exif.Values.Count); + IExifValue software = exif.GetValue(ExifTag.Software); + Assert.NotNull(software); + Assert.Equal("ImageSharp", software.Value); + } + + private static void VerifyTextDataIsPresent(PngMetadata meta) + { + Assert.NotNull(meta); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && + m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && + m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && + m.LanguageTag.Equals("chinese")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag")); + Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort")); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index e26aaf8e65..a50b1059f8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System.IO; -using Xunit; - +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Formats.Png; +using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { @@ -16,104 +15,104 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void GeneralTest(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // does saving a file then reopening mean both files are identical??? using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) + using (var img2 = Image.Load(ms, new PngDecoder())) { ImageComparer.Tolerant().VerifySimilarity(image, img2); + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); } } } - // JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the - // paletted image has alpha of 0 - //[Theory] - //[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - //public void CanSaveIndexedPng(TestImageProvider provider) - // where TPixel : struct, IPixel - //{ - // // does saving a file then reopening mean both files are identical??? - // using (Image image = provider.GetImage()) - // using (MemoryStream ms = new MemoryStream()) - // { - // // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - // image.Save(ms, new PngEncoder() { PaletteSize = 256 }); - // ms.Position = 0; - // using (Image img2 = Image.Load(ms, new PngDecoder())) - // { - // ImageComparer.VerifySimilarity(image, img2, 0.03f); - // } - // } - //} + /* JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the + paletted image has alpha of 0 + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void CanSaveIndexedPng(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // does saving a file then reopening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + image.Save(ms, new PngEncoder() { PaletteSize = 256 }); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + ImageComparer.VerifySimilarity(image, img2, 0.03f); + } + } + }*/ - // JJS: Commented out for now since the test does not take into lossy nature of indexing. - //[Theory] - //[WithTestPatternImages(100, 100, PixelTypes.Color)] - //public void CanSaveIndexedPngTwice(TestImageProvider provider) - // where TPixel : struct, IPixel - //{ - // // does saving a file then reopening mean both files are identical??? - // using (Image source = provider.GetImage()) - // using (MemoryStream ms = new MemoryStream()) - // { - // source.MetaData.Quality = 256; - // source.Save(ms, new PngEncoder(), new PngEncoderOptions { - // Threshold = 200 - // }); - // ms.Position = 0; - // using (Image img1 = Image.Load(ms, new PngDecoder())) - // { - // using (MemoryStream ms2 = new MemoryStream()) - // { - // img1.Save(ms2, new PngEncoder(), new PngEncoderOptions - // { - // Threshold = 200 - // }); - // ms2.Position = 0; - // using (Image img2 = Image.Load(ms2, new PngDecoder())) - // { - // using (PixelAccessor pixels1 = img1.Lock()) - // using (PixelAccessor pixels2 = img2.Lock()) - // { - // for (int y = 0; y < img1.Height; y++) - // { - // for (int x = 0; x < img1.Width; x++) - // { - // Assert.Equal(pixels1[x, y], pixels2[x, y]); - // } - // } - // } - // } - // } - // } - // } - //} + /* JJS: Commented out for now since the test does not take into lossy nature of indexing. + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Color)] + public void CanSaveIndexedPngTwice(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // does saving a file then reopening mean both files are identical??? + using (Image source = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + source.Metadata.Quality = 256; + source.Save(ms, new PngEncoder(), new PngEncoderOptions { + Threshold = 200 + }); + ms.Position = 0; + using (Image img1 = Image.Load(ms, new PngDecoder())) + { + using (MemoryStream ms2 = new MemoryStream()) + { + img1.Save(ms2, new PngEncoder(), new PngEncoderOptions + { + Threshold = 200 + }); + ms2.Position = 0; + using (Image img2 = Image.Load(ms2, new PngDecoder())) + { + using (PixelAccessor pixels1 = img1.Lock()) + using (PixelAccessor pixels2 = img2.Lock()) + { + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Assert.Equal(pixels1[x, y], pixels2[x, y]); + } + } + } + } + } + } + } + }*/ [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void Resize(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // does saving a file then reopening mean both files are identical??? using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("png")); image.Mutate(x => x.Resize(100, 100)); - // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); + // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) + using (var img2 = Image.Load(ms, new PngDecoder())) { ImageComparer.Tolerant().VerifySimilarity(image, img2); } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs new file mode 100644 index 0000000000..6f886b73df --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -0,0 +1,776 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Microsoft.DotNet.RemoteExecutor; + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + using static TestImages.Tga; + + public class TgaDecoderTests + { + private static TgaDecoder TgaDecoder => new TgaDecoder(); + + [Theory] + [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray8BitRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Gray16BitTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Gray16BitRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Bit15, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit15Rle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit16PalRle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24TopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24BottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24RleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24RleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32TopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32TopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32BottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit16RleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24RleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32RleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32RleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32RleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit16PalBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(NoAlphaBits16Bit, PixelTypes.Rgba32)] + [WithFile(NoAlphaBits16BitRle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(NoAlphaBits32Bit, PixelTypes.Rgba32)] + [WithFile(NoAlphaBits32BitRle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + // Using here the reference output instead of the the reference decoder, + // because the reference decoder does not ignore the alpha data here. + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(TgaDecoder)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(TgaDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs new file mode 100644 index 0000000000..6e0fa4a0ea --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + using static TestImages.Tga; + + public class TgaEncoderTests + { + public static readonly TheoryData BitsPerPixel = + new TheoryData + { + TgaBitsPerPixel.Pixel24, + TgaBitsPerPixel.Pixel32 + }; + + public static readonly TheoryData TgaBitsPerPixelFiles = + new TheoryData + { + { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 }, + { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 }, + { Bit24BottomLeft, TgaBitsPerPixel.Pixel24 }, + { Bit32BottomLeft, TgaBitsPerPixel.Pixel32 }, + }; + + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + TgaMetadata meta = output.Metadata.GetTgaMetadata(); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); + } + } + } + } + + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder() + { + Compression = TgaCompression.RunLength + }; + + TestFile testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + TgaMetadata meta = output.Metadata.GetTgaMetadata(); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); + } + } + } + } + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit8_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8) + + // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit8_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8) + + // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] + public void TgaEncoder_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + } + + private static void TestTgaEncoderCore( + TestImageProvider provider, + TgaBitsPerPixel bitsPerPixel, + TgaCompression compression = TgaCompression.None, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression }; + + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + memStream.Position = 0; + using (var encodedImage = (Image)Image.Load(memStream)) + { + TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs new file mode 100644 index 0000000000..4797397e19 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + public class TgaFileHeaderTests + { + private static readonly byte[] Data = + { + 0, + 0, + 15 // invalid tga image type + }; + + private MemoryStream Stream { get; } = new MemoryStream(Data); + + [Fact] + public void ImageLoad_WithInvalidImageType_Throws_UnknownImageFormatException() + { + Assert.Throws(() => + { + using (Image.Load(Configuration.Default, this.Stream, out IImageFormat _)) + { + } + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs new file mode 100644 index 0000000000..48171a77dc --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using ImageMagick; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + public static class TgaTestUtils + { + public static void CompareWithReferenceDecoder( + TestImageProvider provider, + Image image, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) + { + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + } + + var testFile = TestFile.Create(path); + Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + if (useExactComparer) + { + ImageComparer.Exact.VerifySimilarity(magickImage, image); + } + else + { + ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); + } + } + + public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + where TPixel : unmanaged, IPixel + { + using (var magickImage = new MagickImage(fileInfo)) + { + magickImage.AutoOrient(); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + + Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels, + resultPixels.Length); + } + + return result; + } + } + } +} diff --git a/tests/ImageSharp.Tests/GlobalSuppressions.cs b/tests/ImageSharp.Tests/GlobalSuppressions.cs index 2709d32ebc..95fba0dffc 100644 --- a/tests/ImageSharp.Tests/GlobalSuppressions.cs +++ b/tests/ImageSharp.Tests/GlobalSuppressions.cs @@ -1,10 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. - +#pragma warning disable SA1404 // Code analysis suppression should have justification [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.")] - +#pragma warning restore SA1404 // Code analysis suppression should have justification diff --git a/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs new file mode 100644 index 0000000000..9c02dd601e --- /dev/null +++ b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs @@ -0,0 +1,172 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class GraphicOptionsDefaultsExtensionsTests + { + [Fact] + public void SetDefaultOptionsOnProcessingContext() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + context.SetGraphicsOptions(option); + + // sets the prop on the processing context not on the configuration + Assert.Equal(option, context.Properties[typeof(GraphicsOptions)]); + Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); + } + + [Fact] + public void UpdateDefaultOptionsOnProcessingContext_AlwaysNewInstance() + { + var option = new GraphicsOptions() + { + BlendPercentage = 0.9f + }; + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + context.SetGraphicsOptions(option); + + context.SetGraphicsOptions(o => + { + Assert.Equal(0.9f, o.BlendPercentage); // has origional values + o.BlendPercentage = 0.4f; + }); + + var returnedOption = context.GetGraphicsOptions(); + Assert.Equal(0.4f, returnedOption.BlendPercentage); + Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated + } + + [Fact] + public void SetDefaultOptionsOnConfiguration() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + + config.SetGraphicsOptions(option); + + Assert.Equal(option, config.Properties[typeof(GraphicsOptions)]); + } + + [Fact] + public void UpdateDefaultOptionsOnConfiguration_AlwaysNewInstance() + { + var option = new GraphicsOptions() + { + BlendPercentage = 0.9f + }; + var config = new Configuration(); + config.SetGraphicsOptions(option); + + config.SetGraphicsOptions(o => + { + Assert.Equal(0.9f, o.BlendPercentage); // has origional values + o.BlendPercentage = 0.4f; + }); + + var returnedOption = config.GetGraphicsOptions(); + Assert.Equal(0.4f, returnedOption.BlendPercentage); + Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_SettingNullThenReturnsNewInstance() + { + var config = new Configuration(); + + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + config.SetGraphicsOptions((GraphicsOptions)null); + + var options2 = config.GetGraphicsOptions(); + Assert.NotNull(options2); + + // we set it to null should now be a new instance + Assert.NotEqual(options, options2); + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_IgnoreIncorectlyTypesDictionEntry() + { + var config = new Configuration(); + + config.Properties[typeof(GraphicsOptions)] = "wronge type"; + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + Assert.IsType(options); + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_AlwaysReturnsInstance() + { + var config = new Configuration(); + + Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + } + + [Fact] + public void GetDefaultOptionsFromConfiguration_AlwaysReturnsSameValue() + { + var config = new Configuration(); + + var options = config.GetGraphicsOptions(); + var options2 = config.GetGraphicsOptions(); + Assert.Equal(options, options2); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstance() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + var ctxOptions = context.GetGraphicsOptions(); + Assert.NotNull(ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstanceEvenIfSetToNull() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + context.SetGraphicsOptions((GraphicsOptions)null); + var ctxOptions = context.GetGraphicsOptions(); + Assert.NotNull(ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_FallbackToConfigsInstance() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + config.SetGraphicsOptions(option); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + var ctxOptions = context.GetGraphicsOptions(); + Assert.Equal(option, ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_IgnoreIncorectlyTypesDictionEntry() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + context.Properties[typeof(GraphicsOptions)] = "wronge type"; + var options = context.GetGraphicsOptions(); + Assert.NotNull(options); + Assert.IsType(options); + } + } +} diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs index 6ff38626d6..851aba6baf 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -1,21 +1,90 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests { public class GraphicsOptionsTests { + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); + private readonly GraphicsOptions newGraphicsOptions = new GraphicsOptions(); + private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); + + [Fact] + public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); + + [Fact] + public void DefaultGraphicsOptionsAntialias() + { + Assert.True(this.newGraphicsOptions.Antialias); + Assert.True(this.cloneGraphicsOptions.Antialias); + } + + [Fact] + public void DefaultGraphicsOptionsAntialiasSuppixelDepth() + { + const int Expected = 16; + Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth); + Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth); + } + + [Fact] + public void DefaultGraphicsOptionsBlendPercentage() + { + const float Expected = 1F; + Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); + Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); + } + [Fact] - public void IsOpaqueColor() + public void DefaultGraphicsOptionsColorBlendingMode() { - Assert.True(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Red)); - Assert.False(new GraphicsOptions(true, 0.5f).IsOpaqueColorWithoutBlending(Rgba32.Red)); - Assert.False(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Transparent)); - Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Lighten, 1).IsOpaqueColorWithoutBlending(Rgba32.Red)); - Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.DestOver, 1).IsOpaqueColorWithoutBlending(Rgba32.Red)); + const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; + Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); + Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); + } + + [Fact] + public void DefaultGraphicsOptionsAlphaCompositionMode() + { + const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; + Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); + Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); + } + + [Fact] + public void NonDefaultClone() + { + var expected = new GraphicsOptions + { + AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, + Antialias = false, + AntialiasSubpixelDepth = 23, + BlendPercentage = .25F, + ColorBlendingMode = PixelColorBlendingMode.HardLight, + }; + + GraphicsOptions actual = expected.DeepClone(); + + Assert.Equal(expected, actual, GraphicsOptionsComparer); + } + + [Fact] + public void CloneIsDeep() + { + var expected = new GraphicsOptions(); + GraphicsOptions actual = expected.DeepClone(); + + actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; + actual.Antialias = false; + actual.AntialiasSubpixelDepth = 23; + actual.BlendPercentage = .25F; + actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; + + Assert.NotEqual(expected, actual, GraphicsOptionsComparer); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs index 018fabd982..16a27a9cea 100644 --- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; + using Xunit; namespace SixLabors.ImageSharp.Tests.Helpers @@ -131,6 +133,23 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(expected, actual); } + [Theory] + [InlineData(0.2f, 0.7f, 0.1f, 256, 140)] + [InlineData(0.5f, 0.5f, 0.5f, 256, 128)] + [InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)] + [InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)] + public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected) + { + // arrange + var vector = new Vector4(x, y, z, 0.0f); + + // act + int actual = ImageMaths.GetBT709Luminance(ref vector, luminanceLevels); + + // assert + Assert.Equal(expected, actual); + } + // TODO: We need to test all ImageMaths methods! } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs new file mode 100644 index 0000000000..fbe259d2bd --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class ParallelExecutionSettingsTests + { + [Theory] + [InlineData(-3, true)] + [InlineData(-2, true)] + [InlineData(-1, false)] + [InlineData(0, true)] + [InlineData(1, false)] + [InlineData(5, false)] + public void Constructor_MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) + { + if (throws) + { + Assert.Throws( + () => + { + _ = new ParallelExecutionSettings( + maxDegreeOfParallelism, + Configuration.Default.MemoryAllocator); + }); + } + else + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + Configuration.Default.MemoryAllocator); + Assert.Equal(maxDegreeOfParallelism, parallelSettings.MaxDegreeOfParallelism); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs deleted file mode 100644 index aeadfcebb5..0000000000 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Numerics; -using System.Threading; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; - -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Helpers -{ - public class ParallelHelperTests - { - private readonly ITestOutputHelper Output; - - public ParallelHelperTests(ITestOutputHelper output) - { - this.Output = output; - } - - /// - /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength - /// - public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = - new TheoryData - { - { 1, 0, 100, -1, 100 }, - { 2, 0, 9, 5, 4 }, - { 4, 0, 19, 5, 4 }, - { 2, 10, 19, 5, 4 }, - { 4, 0, 200, 50, 50 }, - { 4, 123, 323, 50, 50 }, - { 4, 0, 1201, 301, 298 }, - { 8, 10, 236, 29, 23 } - }; - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - var rectangle = new Rectangle(0, minY, 10, maxY - minY); - - int actualNumberOfSteps = 0; - - ParallelHelper.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); - - Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); - } - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - var rectangle = new Rectangle(0, minY, 10, maxY - minY); - - - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); - var actualData = new int[maxY]; - - ParallelHelper.IterateRows( - rectangle, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); - - Assert.Equal(expectedData, actualData); - } - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - var rectangle = new Rectangle(0, minY, 10, maxY - minY); - - var bufferHashes = new ConcurrentBag(); - - int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - - bufferHashes.Add(buffer.GetHashCode()); - - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); - - Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); - - int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); - Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); - } - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - var rectangle = new Rectangle(0, minY, 10, maxY - minY); - - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); - var actualData = new int[maxY]; - - ParallelHelper.IterateRowsWithTempBuffer( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); - - Assert.Equal(expectedData, actualData); - - } - - public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = - new TheoryData - { - { 2, 200, 50, 2, 1, -1, 2 }, - { 2, 200, 200, 1, 1, -1, 1 }, - { 4, 200, 100, 4, 2, 2, 2 }, - { 4, 300, 100, 8, 3, 3, 2 }, - { 2, 5000, 1, 4500, 1, -1, 4500 }, - { 2, 5000, 1, 5000, 1, -1, 5000 }, - { 2, 5000, 1, 5001, 2, 2501, 2500 }, - }; - - [Theory] - [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] - public void IterateRows_WithEffectiveMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - int width, - int height, - int expectedNumberOfSteps, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - minimumPixelsProcessedPerTask, - Configuration.Default.MemoryAllocator); - - var rectangle = new Rectangle(0, 0, width, height); - - int actualNumberOfSteps = 0; - - ParallelHelper.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); - - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } - - [Theory] - [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - int width, - int height, - int expectedNumberOfSteps, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - minimumPixelsProcessedPerTask, - Configuration.Default.MemoryAllocator); - - var rectangle = new Rectangle(0, 0, width, height); - - int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); - - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } - - public static readonly TheoryData IterateRectangularBuffer_Data = - new TheoryData - { - { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox - { 2, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 1, 226 }, - { 16, 1, 453, 0, 10, 1, 226 }, - }; - - [Theory] - [MemberData(nameof(IterateRectangularBuffer_Data))] - public void IterateRectangularBuffer( - int maxDegreeOfParallelism, - int bufferWidth, - int bufferHeight, - int rectX, - int rectY, - int rectWidth, - int rectHeight) - { - MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; - - using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - { - var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); - - void FillRow(int y, Buffer2D buffer) - { - for (int x = rect.Left; x < rect.Right; x++) - { - buffer[x, y] = new Point(x, y); - } - } - - // Fill Expected data: - for (int y = rectY; y < rect.Bottom; y++) - { - FillRow(y, expected); - } - - // Fill actual data using IterateRows: - var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); - - ParallelHelper.IterateRows(rect, settings, - rows => - { - this.Output.WriteLine(rows.ToString()); - for (int y = rows.Min; y < rows.Max; y++) - { - FillRow(y, actual); - } - }); - - // Assert: - TestImageExtensions.CompareBuffers(expected.Span, actual.Span); - } - } - - [Theory] - [InlineData(0, 10)] - [InlineData(10, 0)] - [InlineData(-10, 10)] - [InlineData(10, -10)] - public void IterateRowsRequiresValidRectangle(int width, int height) - { - var parallelSettings = new ParallelExecutionSettings(); - - var rect = new Rectangle(0, 0, width, height); - - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRows(rect, parallelSettings, rows => { })); - - Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); - } - - [Theory] - [InlineData(0, 10)] - [InlineData(10, 0)] - [InlineData(-10, 10)] - [InlineData(10, -10)] - public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) - { - var parallelSettings = new ParallelExecutionSettings(); - - var rect = new Rectangle(0, 0, width, height); - - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); - - Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); - } - } -} diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs new file mode 100644 index 0000000000..08d64a738d --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -0,0 +1,436 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; +using System.Threading; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class ParallelRowIteratorTests + { + public delegate void RowIntervalAction(RowInterval rows, Span span); + + private readonly ITestOutputHelper output; + + public ParallelRowIteratorTests(ITestOutputHelper output) + { + this.output = output; + } + + /// + /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength + /// + public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = + new TheoryData + { + { 1, 0, 100, -1, 100, 1 }, + { 2, 0, 9, 5, 4, 2 }, + { 4, 0, 19, 5, 4, 4 }, + { 2, 10, 19, 5, 4, 2 }, + { 4, 0, 200, 50, 50, 4 }, + { 4, 123, 323, 50, 50, 4 }, + { 4, 0, 1201, 301, 298, 4 }, + { 8, 10, 236, 29, 23, 8 }, + { 16, 0, 209, 14, 13, 15 }, + { 24, 0, 209, 9, 2, 24 }, + { 32, 0, 209, 7, 6, 30 }, + { 64, 0, 209, 4, 1, 53 }, + }; + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int actualNumberOfSteps = 0; + + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + var actualData = new int[maxY]; + + void RowAction(RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedData, actualData); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int actualNumberOfSteps = 0; + + void RowAction(RowInterval rows, Span buffer) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + var actualData = new int[maxY]; + + void RowAction(RowInterval rows, Span buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedData, actualData); + } + + public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = + new TheoryData + { + { 2, 200, 50, 2, 1, -1, 2 }, + { 2, 200, 200, 1, 1, -1, 1 }, + { 4, 200, 100, 4, 2, 2, 2 }, + { 4, 300, 100, 8, 3, 3, 2 }, + { 2, 5000, 1, 4500, 1, -1, 4500 }, + { 2, 5000, 1, 5000, 1, -1, 5000 }, + { 2, 5000, 1, 5001, 2, 2501, 2500 }, + }; + + [Theory] + [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] + public void IterateRows_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + [Theory] + [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + + void RowAction(RowInterval rows, Span buffer) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + public static readonly TheoryData IterateRectangularBuffer_Data = + new TheoryData + { + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox + { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, + }; + + [Theory] + [MemberData(nameof(IterateRectangularBuffer_Data))] + public void IterateRectangularBuffer( + int maxDegreeOfParallelism, + int bufferWidth, + int bufferHeight, + int rectX, + int rectY, + int rectWidth, + int rectHeight) + { + MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; + + using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + { + var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); + + void FillRow(int y, Buffer2D buffer) + { + for (int x = rect.Left; x < rect.Right; x++) + { + buffer[x, y] = new Point(x, y); + } + } + + // Fill Expected data: + for (int y = rectY; y < rect.Bottom; y++) + { + FillRow(y, expected); + } + + // Fill actual data using IterateRows: + var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); + + void RowAction(RowInterval rows) + { + this.output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rect, + settings, + in operation); + + // Assert: + TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); + } + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsRequiresValidRectangle(int width, int height) + { + var parallelSettings = default(ParallelExecutionSettings); + + var rect = new Rectangle(0, 0, width, height); + + void RowAction(RowInterval rows) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRowIntervals(rect, in parallelSettings, in operation)); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) + { + var parallelSettings = default(ParallelExecutionSettings); + + var rect = new Rectangle(0, 0, width, height); + + void RowAction(RowInterval rows, Span memory) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + { + private readonly Action action; + + public TestRowIntervalOperation(Action action) + => this.action = action; + + public void Invoke(in RowInterval rows) => this.action(rows); + } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly RowIntervalAction action; + + public TestRowIntervalOperation(RowIntervalAction action) + => this.action = action; + + public void Invoke(in RowInterval rows, Span span) + => this.action(rows, span); + } + } +} diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs index 45067f82fe..fd1eb546b5 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -10,31 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class RowIntervalTests { - [Theory] - [InlineData(10, 20, 5, 10)] - [InlineData(1, 10, 0, 10)] - [InlineData(1, 10, 5, 8)] - [InlineData(1, 1, 0, 1)] - [InlineData(10, 20, 9, 10)] - [InlineData(10, 20, 0, 1)] - public void GetMultiRowSpan(int width, int height, int min, int max) - { - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height)) - { - var rows = new RowInterval(min, max); - - Span span = buffer.GetMultiRowSpan(rows); - - ref int expected0 = ref buffer.Span[min * width]; - int expectedLength = (max - min) * width; - - ref int actual0 = ref span[0]; - - Assert.Equal(span.Length, expectedLength); - Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); - } - } - [Fact] public void Slice1() { diff --git a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs index 6c7a1f2752..e2486fb4ae 100644 --- a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs +++ b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs @@ -4,8 +4,8 @@ using System; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Helpers { public class TolerantMathTests @@ -165,4 +165,4 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(expected, actual); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs index f2e98b131a..bc1ffda48f 100644 --- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class Vector4UtilsTests { - private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); [Theory] [InlineData(0)] @@ -21,11 +21,15 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); + Vector4[] expected = source.Select(v => + { + Vector4Utilities.Premultiply(ref v); + return v; + }).ToArray(); - Vector4Utils.Premultiply(source); + Vector4Utilities.Premultiply(source); - Assert.Equal(expected, source, this.ApproximateFloatComparer); + Assert.Equal(expected, source, this.approximateFloatComparer); } [Theory] @@ -36,11 +40,15 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); + Vector4[] expected = source.Select(v => + { + Vector4Utilities.UnPremultiply(ref v); + return v; + }).ToArray(); - Vector4Utils.UnPremultiply(source); + Vector4Utilities.UnPremultiply(source); - Assert.Equal(expected, source, this.ApproximateFloatComparer); + Assert.Equal(expected, source, this.approximateFloatComparer); } } } diff --git a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs index 2d5e81173b..62e2048431 100644 --- a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs +++ b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using SixLabors.ImageSharp.IO; -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests.IO @@ -108,7 +108,6 @@ namespace SixLabors.ImageSharp.Tests.IO for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2) { - Assert.Equal(2, reader.Read(buffer, 0, 2)); Assert.Equal(expected[o], buffer[0]); Assert.Equal(expected[o + 1], buffer[1]); diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs similarity index 100% rename from tests/ImageSharp.Tests/IO/LocalFileSystem.cs rename to tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 035babcb8b..bc2eec79d1 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index 80ab860efe..2b7c1e2c60 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Linq; @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame((ImageFrame)null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); } [Fact] @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(data); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(new Rgba32[0]); }); - Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); + Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); } [Fact] @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.InsertFrame(1, null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); } [Fact] @@ -104,11 +104,7 @@ namespace SixLabors.ImageSharp.Tests { new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 1, 1) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 1, 1) }); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -134,11 +130,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); collection.RemoveFrame(0); Assert.Equal(1, collection.Count); @@ -149,11 +141,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); Assert.Equal(collection.RootFrame, collection[0]); } @@ -163,11 +151,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); Assert.Equal(2, collection.Count); } @@ -177,11 +161,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); collection.Dispose(); @@ -193,11 +173,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); collection.Dispose(); @@ -206,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests framesSnapShot, f => { - // the pixel source of the frame is null after its been disposed. + // The pixel source of the frame is null after its been disposed. Assert.Null(f.PixelBuffer); }); } @@ -214,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void CloneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -222,7 +198,9 @@ namespace SixLabors.ImageSharp.Tests using (Image cloned = img.Frames.CloneFrame(0)) { Assert.Equal(2, img.Frames.Count); - cloned.ComparePixelBufferTo(img.GetPixelSpan()); + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + + cloned.ComparePixelBufferTo(imgSpan); } } } @@ -230,11 +208,12 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void ExtractFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { - var sourcePixelData = img.GetPixelSpan().ToArray(); + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + TPixel[] sourcePixelData = imgSpan.ToArray(); img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); using (Image cloned = img.Frames.ExportFrame(0)) @@ -257,16 +236,17 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Rgba32.HotPink); + this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); + this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); } [Fact] public void AddFrameFromPixelData() { - var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray(); + Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imgSpan)); + var pixelData = imgSpan.ToArray(); this.Image.Frames.AddFrame(pixelData); Assert.Equal(2, this.Image.Frames.Count); } @@ -275,8 +255,10 @@ namespace SixLabors.ImageSharp.Tests public void AddFrame_clones_sourceFrame() { var otherFrame = new ImageFrame(Configuration.Default, 10, 10); - var addedFrame = this.Image.Frames.AddFrame(otherFrame); - addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan()); + ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); + + Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); + addedFrame.ComparePixelBufferTo(otherFrameSpan); Assert.NotEqual(otherFrame, addedFrame); } @@ -284,8 +266,10 @@ namespace SixLabors.ImageSharp.Tests public void InsertFrame_clones_sourceFrame() { var otherFrame = new ImageFrame(Configuration.Default, 10, 10); - var addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); - addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan()); + ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); + + Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); + addedFrame.ComparePixelBufferTo(otherFrameSpan); Assert.NotEqual(otherFrame, addedFrame); } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index e972012e29..a05b428e15 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Linq; @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); var actualFrame = (ImageFrame)this.Collection[1]; @@ -55,13 +55,12 @@ namespace SixLabors.ImageSharp.Tests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); var actualFrame = (ImageFrame)this.Collection[0]; actualFrame.ComparePixelBufferTo(expectedAllBlue); - } [Fact] @@ -91,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] @@ -115,14 +114,12 @@ namespace SixLabors.ImageSharp.Tests this.Collection.InsertFrame(1, null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } - [Fact] public void RemoveAtFrame_ThrowIfRemovingLastFrame() { - InvalidOperationException ex = Assert.Throws( () => { @@ -149,7 +146,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] public void CloneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -162,7 +159,8 @@ namespace SixLabors.ImageSharp.Tests var expectedClone = (Image)cloned; - expectedClone.ComparePixelBufferTo(img.GetPixelSpan()); + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + expectedClone.ComparePixelBufferTo(imgSpan); } } } @@ -170,11 +168,12 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void ExtractFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { - var sourcePixelData = img.GetPixelSpan().ToArray(); + Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + var sourcePixelData = imgSpan.ToArray(); ImageFrameCollection nonGenericFrameCollection = img.Frames; @@ -204,13 +203,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Rgba32.HotPink); + this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); var frame = (ImageFrame)this.Image.Frames[1]; - frame.ComparePixelBufferTo(Rgba32.HotPink); + frame.ComparePixelBufferTo(Color.HotPink); } [Fact] @@ -267,16 +266,16 @@ namespace SixLabors.ImageSharp.Tests /// /// Integration test for end-to end API validation. /// + /// The pixel type of the image. [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (var dest = new Image(source.GetConfiguration(), source.Width, source.Height)) { // Giphy.gif has 5 frames - ImportFrameAs(source.Frames, dest.Frames, 0); ImportFrameAs(source.Frames, dest.Frames, 1); ImportFrameAs(source.Frames, dest.Frames, 2); @@ -297,7 +296,7 @@ namespace SixLabors.ImageSharp.Tests } private static void ImportFrameAs(ImageFrameCollection source, ImageFrameCollection destination, int index) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image temp = source.CloneFrame(index)) { @@ -311,9 +310,8 @@ namespace SixLabors.ImageSharp.Tests private static void CompareGifMetadata(ImageFrame a, ImageFrame b) { // TODO: all metadata classes should be equatable! - - GifFrameMetadata aData = a.Metadata.GetFormatMetadata(GifFormat.Instance); - GifFrameMetadata bData = b.Metadata.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata aData = a.Metadata.GetGifMetadata(); + GifFrameMetadata bData = b.Metadata.GetGifMetadata(); Assert.Equal(aData.DisposalMethod, bData.DisposalMethod); Assert.Equal(aData.FrameDelay, bData.FrameDelay); diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs index d475513fa6..d81defbcda 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.PixelFormats; @@ -7,6 +10,7 @@ namespace SixLabors.ImageSharp.Tests public abstract partial class ImageFrameCollectionTests : IDisposable { protected Image Image { get; } + protected ImageFrameCollection Collection { get; } public ImageFrameCollectionTests() diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs new file mode 100644 index 0000000000..58d7d7981c --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFrameTests + { + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + Rgba32 val = frame[3, 4]; + Assert.Equal(default(Rgba32), val); + frame[3, 4] = Color.Red; + val = frame[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { false, -1 }, + { false, 10 }, + { true, -1 }, + { true, 10 }, + }; + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Get_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = frame[x, 3]); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[x, 3] = default); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeY(bool enforceDisco, int y) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index e1c4a419e1..28196c0da1 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 25bc3f6049..156e51578b 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -3,22 +3,21 @@ using System; using System.IO; - +using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -using Moq; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// - /// Tests the class. + /// Tests the class. /// public class ImageSaveTests : IDisposable { - private readonly Image Image; + private readonly Image image; private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; @@ -42,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests }; config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); - this.Image = new Image(config, 1, 1); + this.image = new Image(config, 1, 1); } [Fact] @@ -50,38 +49,37 @@ namespace SixLabors.ImageSharp.Tests { var stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); - this.Image.Save("path.png"); + this.image.Save("path.png"); - this.encoder.Verify(x => x.Encode(this.Image, stream)); + this.encoder.Verify(x => x.Encode(this.image, stream)); } - [Fact] public void SavePathWithEncoder() { var stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + this.image.Save("path.jpg", this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); + this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); } [Fact] public void ToBase64String() { - string str = this.Image.ToBase64String(this.localImageFormat.Object); + string str = this.image.ToBase64String(this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); + this.encoder.Verify(x => x.Encode(this.image, It.IsAny())); } [Fact] public void SaveStreamWithMime() { var stream = new MemoryStream(); - this.Image.Save(stream, this.localImageFormat.Object); + this.image.Save(stream, this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.Image, stream)); + this.encoder.Verify(x => x.Encode(this.image, stream)); } [Fact] @@ -89,14 +87,14 @@ namespace SixLabors.ImageSharp.Tests { var stream = new MemoryStream(); - this.Image.Save(stream, this.encoderNotInFormat.Object); + this.image.Save(stream, this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); + this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); } public void Dispose() { - this.Image.Dispose(); + this.image.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index 96747b0d29..dcf4dcfe84 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -6,8 +6,8 @@ using System.IO; using SixLabors.ImageSharp.Formats; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public partial class ImageTests diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs new file mode 100644 index 0000000000..2be9504079 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats; + +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + /// + /// Tests the class. + /// + public class Identify : ImageLoadTestBase + { + private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); + + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + + private byte[] ByteArray => this.DataStream.ToArray(); + + private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; + + private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; + + private static readonly IImageFormat ExpectedGlobalFormat = + Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); + + [Fact] + public void FromBytes_GlobalConfiguration() + { + IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + + [Fact] + public void FromBytes_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.ByteArray, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromFileSystemPath_GlobalConfiguration() + { + IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + + [Fact] + public void FromFileSystemPath_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.MockFilePath, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromStream_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageInfo info = Image.Identify(stream, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + } + + [Fact] + public void FromStream_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void WhenNoMatchingFormatFound_ReturnsNull() + { + IImageInfo info = Image.Identify(new Configuration(), this.DataStream, out IImageFormat type); + + Assert.Null(info); + Assert.Null(type); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index ec6705d0ec..d010f60236 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests public abstract class ImageLoadTestBase : IDisposable { protected Image localStreamReturnImageRgba32; - + protected Image localStreamReturnImageAgnostic; protected Mock localDecoder; @@ -26,9 +26,11 @@ namespace SixLabors.ImageSharp.Tests protected Mock localImageFormatMock; + protected Mock localImageInfoMock; + protected readonly string MockFilePath = Guid.NewGuid().ToString(); - internal readonly Mock localFileSystemMock = new Mock(); + internal readonly Mock LocalFileSystemMock = new Mock(); protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); @@ -53,11 +55,14 @@ namespace SixLabors.ImageSharp.Tests this.localStreamReturnImageRgba32 = new Image(1, 1); this.localStreamReturnImageAgnostic = new Image(1, 1); + this.localImageInfoMock = new Mock(); this.localImageFormatMock = new Mock(); - this.localDecoder = new Mock(); + var detector = new Mock(); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); + this.localDecoder = detector.As(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); - + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -68,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests } }) .Returns(this.localStreamReturnImageRgba32); - + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -79,11 +84,8 @@ namespace SixLabors.ImageSharp.Tests } }) .Returns(this.localStreamReturnImageAgnostic); - - this.LocalConfiguration = new Configuration - { - }; + this.LocalConfiguration = new Configuration(); this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); @@ -92,18 +94,18 @@ namespace SixLabors.ImageSharp.Tests this.Marker = Guid.NewGuid().ToByteArray(); this.DataStream = this.TestFormat.CreateStream(this.Marker); - this.localFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); + this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); this.topLevelFileSystem.AddFile(this.MockFilePath, this.DataStream); - this.LocalConfiguration.FileSystem = this.localFileSystemMock.Object; + this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; } public void Dispose() { - // clean up the global object; + // Clean up the global object; this.localStreamReturnImageRgba32?.Dispose(); this.localStreamReturnImageAgnostic?.Dispose(); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 7a5fa87290..399652851c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Tests [InlineData(true)] public void FromPixels(bool useSpan) { - Rgba32[] data = { Rgba32.Black, Rgba32.White, Rgba32.White, Rgba32.Black, }; + Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, }; using (Image img = useSpan ? Image.LoadPixelData(data.AsSpan(), 2, 2) : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } @@ -48,13 +48,13 @@ namespace SixLabors.ImageSharp.Tests : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index 58e19c9f70..cb3400758f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws( () => { - Image.Load(this.TopLevelConfiguration,(string)null); + Image.Load(this.TopLevelConfiguration, (string)null); }); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs index 4d3a229c55..4c6b92100e 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -1,12 +1,11 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using Xunit; @@ -17,12 +16,12 @@ namespace SixLabors.ImageSharp.Tests public class Load_FileSystemPath_UseDefaultConfiguration { private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); - + private static void VerifyDecodedImage(Image img) { Assert.Equal(new Size(127, 64), img.Size()); } - + [Fact] public void Path_Specific() { @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Path_Agnostic() { @@ -40,8 +39,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - - + [Fact] public void Path_Decoder_Specific() { @@ -50,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Path_Decoder_Agnostic() { @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Path_OutFormat_Specific() { @@ -79,6 +77,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(format); } } + [Fact] public void WhenFileNotFound_Throws() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs index 19887d9bcc..b8ed6e75b5 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -6,7 +6,6 @@ using System; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using Xunit; @@ -17,14 +16,14 @@ namespace SixLabors.ImageSharp.Tests public class Load_FromBytes_UseGlobalConfiguration { private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - + private static Span ByteSpan => new Span(ByteArray); private static void VerifyDecodedImage(Image img) { Assert.Equal(new Size(127, 64), img.Size()); } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -47,7 +46,6 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [InlineData(false)] [InlineData(true)] @@ -82,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(format); } } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -97,4 +95,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs index 980ed17ceb..0c722b4d66 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -7,7 +7,6 @@ using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using Xunit; @@ -18,14 +17,14 @@ namespace SixLabors.ImageSharp.Tests public class Load_FromStream_UseDefaultConfiguration : IDisposable { private static readonly byte[] Data = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - + private MemoryStream Stream { get; } = new MemoryStream(Data); - + private static void VerifyDecodedImage(Image img) { Assert.Equal(new Size(127, 64), img.Size()); } - + [Fact] public void Stream_Specific() { @@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_Agnostic() { @@ -43,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_OutFormat_Specific() { @@ -53,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(format); } } - + [Fact] public void Stream_Decoder_Specific() { @@ -62,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_Decoder_Agnostic() { @@ -71,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_OutFormat_Agnostic() { @@ -88,4 +87,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs index e00a70e392..dc65ecfef9 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.IO; @@ -12,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { using SixLabors.ImageSharp.Formats; diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index ea99573141..e0152558b5 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,10 +9,9 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Shapes; -using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests /// A exposing the locked pixel memory of a instance. /// TODO: This should be an example in https://github.com/SixLabors/Samples /// - class BitmapMemoryManager : MemoryManager + public class BitmapMemoryManager : MemoryManager { private readonly Bitmap bitmap; @@ -42,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests } this.bitmap = bitmap; - var rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height); + var rectangle = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height); this.bmpData = bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, bitmap.PixelFormat); this.length = bitmap.Width * bitmap.Height; } @@ -60,13 +59,13 @@ namespace SixLabors.ImageSharp.Tests { this.bitmap.UnlockBits(this.bmpData); } - + this.IsDisposed = true; } public override unsafe Span GetSpan() { - void* ptr = (void*) this.bmpData.Scan0; + void* ptr = (void*)this.bmpData.Scan0; return new Span(ptr, this.length); } @@ -92,7 +91,8 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - ref Rgba32 pixel0 = ref image.GetPixelSpan()[0]; + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + ref Rgba32 pixel0 = ref imageSpan[0]; Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); Assert.Equal(cfg, image.GetConfiguration()); @@ -118,8 +118,13 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { - Assert.Equal(memory, image.GetPixelMemory()); - image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10))); + Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + imageSpan.Fill(bg); + for (var i = 10; i < 20; i++) + { + image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + } } Assert.False(memoryManager.IsDisposed); @@ -149,8 +154,13 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { - Assert.Equal(memoryManager.Memory, image.GetPixelMemory()); - image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10))); + Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + imageSpan.Fill(bg); + for (var i = 10; i < 20; i++) + { + image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + } } Assert.True(memoryManager.IsDisposed); @@ -164,7 +174,7 @@ namespace SixLabors.ImageSharp.Tests } private static bool ShouldSkipBitmapTest => - !TestEnvironment.Is64BitProcess || TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"; + !TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 60384c0578..6bba8b15c7 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,14 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// @@ -25,7 +27,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.Equal(11*23, image.GetPixelSpan().Length); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.Equal(11 * 23, imageSpan.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(Configuration.Default, image.GetConfiguration()); @@ -41,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.Equal(11 * 23, image.GetPixelSpan().Length); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.Equal(11 * 23, imageSpan.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(configuration, image.GetConfiguration()); @@ -52,13 +56,14 @@ namespace SixLabors.ImageSharp.Tests public void Configuration_Width_Height_BackgroundColor() { Configuration configuration = Configuration.Default.Clone(); - Rgba32 color = Rgba32.Aquamarine; + Rgba32 color = Color.Aquamarine; using (var image = new Image(configuration, 11, 23, color)) { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.Equal(11 * 23, image.GetPixelSpan().Length); + Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.Equal(11 * 23, imageSpan.Length); image.ComparePixelBufferTo(color); Assert.Equal(configuration, image.GetConfiguration()); @@ -74,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); var metadata = new ImageMetadata(); - using (Image image = Image.CreateUninitialized(configuration, 21, 22, metadata)) + using (var image = Image.CreateUninitialized(configuration, 21, 22, metadata)) { Assert.Equal(21, image.Width); Assert.Equal(22, image.Height); @@ -85,5 +90,84 @@ namespace SixLabors.ImageSharp.Tests } } } + + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + Rgba32 val = image[3, 4]; + Assert.Equal(default(Rgba32), val); + image[3, 4] = Color.Red; + val = image[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { false, -1 }, + { false, 10 }, + { true, -1 }, + { true, 10 }, + }; + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Get_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[x, 3]); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[x, 3] = default); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeY(bool enforceDisco, int y) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs new file mode 100644 index 0000000000..7352ddd7df --- /dev/null +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class LargeImageIntegrationTests + { + [Theory(Skip = "For local testing only.")] + [WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)] + public void CreateAndResize(TestImageProvider provider) + { + using Image image = provider.GetImage(); + image.Mutate(c => c.Resize(1000, 1000)); + image.DebugSave(provider); + } + + [Theory] + [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] + public void GetSingleSpan(TestImageProvider provider) + { + provider.LimitAllocatorBufferCapacity().InPixels(10); + using Image image = provider.GetImage(); + Assert.False(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.False(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imageFrameSpan)); + } + } +} diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 67804a18fd..bde5f7b6a5 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; -using SixLabors.Primitives; using Xunit; diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 1ac5f8085a..fdb280ca99 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -1,27 +1,27 @@ - + - netcoreapp2.1;net462;net472 + netcoreapp3.1;netcoreapp2.1;net472 True - latest - full - portable True SixLabors.ImageSharp.Tests AnyCPU;x64;x86 SixLabors.ImageSharp.Tests + + true - - - - + + - + + + + @@ -36,10 +36,5 @@ - - - - - diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject b/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject deleted file mode 100644 index f015b4b86e..0000000000 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject +++ /dev/null @@ -1,9 +0,0 @@ - - - False - UseStaticAnalysis - - False - False - - \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Issues/Issue412.cs b/tests/ImageSharp.Tests/Issues/Issue412.cs deleted file mode 100644 index 5a590018e5..0000000000 --- a/tests/ImageSharp.Tests/Issues/Issue412.cs +++ /dev/null @@ -1,51 +0,0 @@ -using SixLabors.Primitives; - -using Xunit; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests.Issues -{ - public class Issue412 - { - [Theory] - [WithBlankImages(40, 30, PixelTypes.Rgba32)] - public void AllPixelsExpectedToBeRedWhenAntialiasedDisabled(TestImageProvider provider) where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate( - context => - { - for (var i = 0; i < 40; ++i) - { - context.DrawLines( - new GraphicsOptions(false), - Color.Black, - 1, - new PointF(i, 0.1066f), - new PointF(i, 10.1066f)); - - context.DrawLines( - new GraphicsOptions(false), - Color.Red, - 1, - new PointF(i, 15.1066f), - new PointF(i, 25.1066f)); - } - }); - - image.DebugSave(provider); - for (var y = 15; y < 25; y++) - { - for (var x = 0; x < 40; x++) - { - TPixel red = Color.Red.ToPixel(); - - Assert.True(red.Equals(image[x, y]), $"expected {Color.Red} but found {image[x, y]} at [{x}, {y}]"); - } - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Issues/Issue594.cs b/tests/ImageSharp.Tests/Issues/Issue594.cs index 927f0a5edc..8ddd9caf83 100644 --- a/tests/ImageSharp.Tests/Issues/Issue594.cs +++ b/tests/ImageSharp.Tests/Issues/Issue594.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,8 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Issues public void NormalizedByte4() { // Test PackedValue - Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); - Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); // Test ToVector4 @@ -46,48 +49,48 @@ namespace SixLabors.ImageSharp.Tests.Issues n.FromRgba32(new Rgba32(141, 90, 192, 39)); Assert.Equal(0xA740DA0D, n.PackedValue); - Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(958796544U, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - //var rgb = default(Rgb24); - //var rgba = default(Rgba32); - //var bgr = default(Bgr24); - //var bgra = default(Bgra32); - //var argb = default(Argb32); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new NormalizedByte4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(141, 90, 192)); + // new NormalizedByte4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(141, 90, 192)); - //new NormalizedByte4(x, y, z, w).ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); + // new NormalizedByte4(x, y, z, w).ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); - //new NormalizedByte4(x, y, z, w).ToBgr24(ref bgr); - //Assert.Equal(bgr, new Bgr24(141, 90, 192)); + // new NormalizedByte4(x, y, z, w).ToBgr24(ref bgr); + // Assert.Equal(bgr, new Bgr24(141, 90, 192)); - //new NormalizedByte4(x, y, z, w).ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) + // new NormalizedByte4(x, y, z, w).ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) - //new NormalizedByte4(x, y, z, w).ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(141, 90, 192, 39)); + // new NormalizedByte4(x, y, z, w).ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(141, 90, 192, 39)); // http://community.monogame.net/t/normalizedbyte4-texture2d-gives-different-results-from-xna/8012/8 - //var r = default(NormalizedByte4); - //r.FromRgba32(new Rgba32(9, 115, 202, 127)); - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); - - //r.PackedValue = 0xff4af389; - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); - - //r = default(NormalizedByte4); - //r.FromArgb32(new Argb32(9, 115, 202, 127)); - //r.ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(9, 115, 202, 127)); - - //r = default(NormalizedByte4); - //r.FromBgra32(new Bgra32(9, 115, 202, 127)); - //r.ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); + // var r = default(NormalizedByte4); + // r.FromRgba32(new Rgba32(9, 115, 202, 127)); + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + // r.PackedValue = 0xff4af389; + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + // r = default(NormalizedByte4); + // r.FromArgb32(new Argb32(9, 115, 202, 127)); + // r.ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(9, 115, 202, 127)); + + // r = default(NormalizedByte4); + // r.FromBgra32(new Bgra32(9, 115, 202, 127)); + // r.ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); } // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue @@ -96,8 +99,8 @@ namespace SixLabors.ImageSharp.Tests.Issues public void NormalizedShort4() { // Test PackedValue - Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); // Test ToVector4 @@ -117,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Issues // Test FromScaledVector4. var pixel = default(NormalizedShort4); pixel.FromScaledVector4(scaled); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); // Test Ordering float x = 0.1f; @@ -125,43 +128,43 @@ namespace SixLabors.ImageSharp.Tests.Issues float z = 0.5f; float w = -0.7f; Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).PackedValue); - Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(4150390751449251866UL, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - //var rgb = default(Rgb24); - //var rgba = default(Rgba32); - //var bgr = default(Bgr24); - //var bgra = default(Bgra32); - //var argb = default(Argb32); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(141, 90, 192)); + // new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(141, 90, 192)); - //new NormalizedShort4(x, y, z, w).ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) + // new NormalizedShort4(x, y, z, w).ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) - //new NormalizedShort4(x, y, z, w).ToBgr24(ref bgr); - //Assert.Equal(bgr, new Bgr24(141, 90, 192)); + // new NormalizedShort4(x, y, z, w).ToBgr24(ref bgr); + // Assert.Equal(bgr, new Bgr24(141, 90, 192)); - //new NormalizedShort4(x, y, z, w).ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); + // new NormalizedShort4(x, y, z, w).ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); - //new NormalizedShort4(x, y, z, w).ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(141, 90, 192, 39)); + // new NormalizedShort4(x, y, z, w).ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(141, 90, 192, 39)); - //var r = default(NormalizedShort4); - //r.FromRgba32(new Rgba32(9, 115, 202, 127)); - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + // var r = default(NormalizedShort4); + // r.FromRgba32(new Rgba32(9, 115, 202, 127)); + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); - //r = default(NormalizedShort4); - //r.FromBgra32(new Bgra32(9, 115, 202, 127)); - //r.ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); + // r = default(NormalizedShort4); + // r.FromBgra32(new Bgra32(9, 115, 202, 127)); + // r.ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); - //r = default(NormalizedShort4); - //r.FromArgb32(new Argb32(9, 115, 202, 127)); - //r.ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(9, 115, 202, 127)); + // r = default(NormalizedShort4); + // r.FromArgb32(new Argb32(9, 115, 202, 127)); + // r.ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(9, 115, 202, 127)); } // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue @@ -170,8 +173,8 @@ namespace SixLabors.ImageSharp.Tests.Issues public void Short4() { // Test the limits. - Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x0UL, new Short4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); // Test ToVector4. @@ -193,7 +196,7 @@ namespace SixLabors.ImageSharp.Tests.Issues // Test FromScaledVector4. var pixel = default(Short4); pixel.FromScaledVector4(scaled); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); // Test clamping. Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); @@ -210,43 +213,43 @@ namespace SixLabors.ImageSharp.Tests.Issues y = 12653; z = 29623; w = 193; - Assert.Equal((ulong)0x00c173b7316d2d1b, new Short4(x, y, z, w).PackedValue); + Assert.Equal(0x00c173b7316d2d1bUL, new Short4(x, y, z, w).PackedValue); - //var rgb = default(Rgb24); - //var rgba = default(Rgba32); - //var bgr = default(Bgr24); - //var bgra = default(Bgra32); - //var argb = default(Argb32); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new Short4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(172, 177, 243)); // this assert fails in Release build on linux (#594) + // new Short4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(172, 177, 243)); // this assert fails in Release build on linux (#594) - //new Short4(x, y, z, w).ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(172, 177, 243, 128)); + // new Short4(x, y, z, w).ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(172, 177, 243, 128)); - //new Short4(x, y, z, w).ToBgr24(ref bgr); - //Assert.Equal(bgr, new Bgr24(172, 177, 243)); + // new Short4(x, y, z, w).ToBgr24(ref bgr); + // Assert.Equal(bgr, new Bgr24(172, 177, 243)); - //new Short4(x, y, z, w).ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(172, 177, 243, 128)); + // new Short4(x, y, z, w).ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(172, 177, 243, 128)); - //new Short4(x, y, z, w).ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(172, 177, 243, 128)); + // new Short4(x, y, z, w).ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(172, 177, 243, 128)); - //var r = default(Short4); - //r.FromRgba32(new Rgba32(20, 38, 0, 255)); - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(20, 38, 0, 255)); + // var r = default(Short4); + // r.FromRgba32(new Rgba32(20, 38, 0, 255)); + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(20, 38, 0, 255)); - //r = default(Short4); - //r.FromBgra32(new Bgra32(20, 38, 0, 255)); - //r.ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(20, 38, 0, 255)); + // r = default(Short4); + // r.FromBgra32(new Bgra32(20, 38, 0, 255)); + // r.ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(20, 38, 0, 255)); - //r = default(Short4); - //r.FromArgb32(new Argb32(20, 38, 0, 255)); - //r.ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(20, 38, 0, 255)); + // r = default(Short4); + // r.FromArgb32(new Argb32(20, 38, 0, 255)); + // r.ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(20, 38, 0, 255)); } // Comparison helpers with small tolerance to allow for floating point rounding during computations. diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs new file mode 100644 index 0000000000..8db79fca08 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -0,0 +1,284 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class ArrayPoolMemoryAllocatorTests + { + private const int MaxPooledBufferSizeInBytes = 2048; + + private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; + + /// + /// Gets the SUT for in-process tests. + /// + private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture(); + + /// + /// Gets the SUT for tests executed by , + /// recreated in each external process. + /// + private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); + + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) + { + } + } + + public class Constructor + { + [Fact] + public void WhenBothParametersPassedByUser() + { + var mgr = new ArrayPoolMemoryAllocator(1111, 666); + Assert.Equal(1111, mgr.MaxPoolSizeInBytes); + Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); + } + + [Fact] + public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() + { + var mgr = new ArrayPoolMemoryAllocator(5000); + Assert.Equal(5000, mgr.MaxPoolSizeInBytes); + Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); + } + + [Fact] + public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() + { + Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); + } + } + + [Theory] + [InlineData(32)] + [InlineData(512)] + [InlineData(MaxPooledBufferSizeInBytes - 1)] + public void SmallBuffersArePooled_OfByte(int size) + { + Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); + } + + [Theory] + [InlineData(128 * 1024 * 1024)] + [InlineData(MaxPooledBufferSizeInBytes + 1)] + public void LargeBuffersAreNotPooled_OfByte(int size) + { + static void RunTest(string sizeStr) + { + int size = int.Parse(sizeStr); + StaticFixture.CheckIsRentingPooledBuffer(size); + } + + RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose(); + } + + [Fact] + public unsafe void SmallBuffersArePooled_OfBigValueType() + { + int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; + + Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(count)); + } + + [Fact] + public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() + { + int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; + + Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer(count)); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) + { + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) + { + BufferExtensions.GetSpan(firstAlloc).Fill(666); + } + + using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) + { + int expected = options == AllocationOptions.Clean ? 0 : 666; + Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) + { + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + IMemoryOwner buffer = memoryAllocator.Allocate(32); + ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer)); + + if (!keepBufferAlive) + { + buffer.Dispose(); + } + + memoryAllocator.ReleaseRetainedResources(); + + buffer = memoryAllocator.Allocate(32); + + Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer))); + } + + [Fact] + public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() + { + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + IMemoryOwner buffer = memoryAllocator.Allocate(32); + memoryAllocator.ReleaseRetainedResources(); + buffer.Dispose(); + } + + [Fact] + public void AllocationOverLargeArrayThreshold_UsesDifferentPool() + { + static void RunTest() + { + const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); + + IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); + ref int ptr2Small = ref BufferExtensions.GetReference(small); + small.Dispose(); + + IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); + + Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large))); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void CreateWithAggressivePooling() + { + static void RunTest() + { + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(4096 * 4096)); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void CreateDefault() + { + static void RunTest() + { + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); + + Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void CreateWithModeratePooling() + { + static void RunTest() + { + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); + Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(1024 * 16)); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Theory] + [InlineData(-1)] + [InlineData(-111)] + public void Allocate_Negative_Throws_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); + Assert.Equal("length", ex.ParamName); + } + + [Fact] + public void AllocateZero() + { + using IMemoryOwner buffer = this.LocalFixture.MemoryAllocator.Allocate(0); + Assert.Equal(0, buffer.Memory.Length); + } + + [Theory] + [InlineData(101)] + [InlineData((int.MaxValue / SizeOfLargeStruct) - 1)] + [InlineData(int.MaxValue / SizeOfLargeStruct)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 137)] + public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length) + { + this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct; + Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); + } + + [Theory] + [InlineData(-1)] + public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => + this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length)); + Assert.Equal("length", ex.ParamName); + } + + private class MemoryAllocatorFixture + { + public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = + new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); + + /// + /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. + /// + public bool CheckIsRentingPooledBuffer(int length) + where T : struct + { + IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); + ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer); + buffer.Dispose(); + + buffer = this.MemoryAllocator.Allocate(length); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer)); + buffer.Dispose(); + + return sameBuffers; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct SmallStruct + { + private readonly uint dummy; + } + + private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; + + [StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] + private struct LargeStruct + { + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs new file mode 100644 index 0000000000..9f8543fff0 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + internal static class BufferExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetSpan(this IMemoryOwner buffer) + => buffer.Memory.Span; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Length(this IMemoryOwner buffer) + => buffer.GetSpan().Length; + + public static ref T GetReference(this IMemoryOwner buffer) + where T : struct => + ref MemoryMarshal.GetReference(buffer.GetSpan()); + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs new file mode 100644 index 0000000000..6465e0b81d --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -0,0 +1,319 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + /// + /// Inherit this class to test an implementation (provided by ). + /// + public abstract class BufferTestSuite + { + protected BufferTestSuite(MemoryAllocator memoryAllocator) + { + this.MemoryAllocator = memoryAllocator; + } + + protected MemoryAllocator MemoryAllocator { get; } + + public struct CustomStruct : IEquatable + { + public long A; + + public byte B; + + public float C; + + public CustomStruct(long a, byte b, float c) + { + this.A = a; + this.B = b; + this.C = c; + } + + public bool Equals(CustomStruct other) + { + return this.A == other.A && this.B == other.B && this.C.Equals(other.C); + } + + public override bool Equals(object obj) + { + return obj is CustomStruct other && this.Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = this.A.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + hashCode = (hashCode * 397) ^ this.C.GetHashCode(); + return hashCode; + } + } + } + + public static readonly TheoryData LenthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_byte(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_float(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_CustomStruct(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + private void TestHasCorrectLength(int desiredLength) + where T : struct + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) + { + Assert.Equal(desiredLength, buffer.GetSpan().Length); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_byte(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength, false); + this.TestCanAllocateCleanBuffer(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_double(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + private IMemoryOwner Allocate(int desiredLength, AllocationOptions options, bool managedByteBuffer) + where T : struct + { + if (managedByteBuffer) + { + if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IMemoryOwner buffer)) + { + throw new InvalidOperationException("typeof(T) != typeof(byte)"); + } + + return buffer; + } + + return this.MemoryAllocator.Allocate(desiredLength, options); + } + + private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + ReadOnlySpan expected = new T[desiredLength]; + + for (int i = 0; i < 10; i++) + { + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.Clean, testManagedByteBuffer)) + { + Assert.True(buffer.GetSpan().SequenceEqual(expected)); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + } + + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + where T : struct + { + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + { + ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); + ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); + ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan()); + + Assert.True(Unsafe.AreSame(ref a, ref b)); + Assert.True(Unsafe.AreSame(ref b, ref c)); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_float(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_byte(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), false); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + } + + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + where T : struct + { + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + { + T[] expectedVals = new T[buffer.Length()]; + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.GetSpan(); + expectedVals[i] = getExpectedValue(i); + span[i] = expectedVals[i]; + } + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.GetSpan(); + Assert.Equal(expectedVals[i], span[i]); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength, false); + this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + var dummy = default(T); + + using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + { + Assert.ThrowsAny( + () => + { + Span span = buffer.GetSpan(); + dummy = span[desiredLength]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.GetSpan(); + dummy = span[desiredLength + 1]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.GetSpan(); + dummy = span[desiredLength + 42]; + }); + } + + return dummy; + } + + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(1024)] + [InlineData(6666)] + public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) + { + using (IManagedByteBuffer buffer = this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength)) + { + ref byte array0 = ref buffer.Array[0]; + ref byte span0 = ref buffer.GetReference(); + + Assert.True(Unsafe.AreSame(ref span0, ref array0)); + Assert.True(buffer.Array.Length >= buffer.GetSpan().Length); + } + } + + [Fact] + public void GetMemory_ReturnsValidMemory() + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) + { + Span span0 = buffer.GetSpan(); + span0[10].A = 30; + Memory memory = buffer.Memory; + + Assert.Equal(42, memory.Length); + Span span1 = memory.Span; + + Assert.Equal(42, span1.Length); + Assert.Equal(30, span1[10].A); + } + } + + [Fact] + public unsafe void GetMemory_ResultIsPinnable() + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) + { + Span span0 = buffer.GetSpan(); + span0[10] = 30; + + Memory memory = buffer.Memory; + + using (MemoryHandle h = memory.Pin()) + { + int* ptr = (int*)h.Pointer; + Assert.Equal(30, ptr[10]); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs new file mode 100644 index 0000000000..9e14bd1db6 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class SimpleGcMemoryAllocatorTests + { + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new SimpleGcMemoryAllocator()) + { + } + } + + protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new SimpleGcMemoryAllocator(); + + [Theory] + [InlineData(-1)] + public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.Allocate(length)); + Assert.Equal("length", ex.ParamName); + } + + [Theory] + [InlineData(-1)] + public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); + Assert.Equal("length", ex.ParamName); + } + + [StructLayout(LayoutKind.Explicit, Size = 512)] + private struct BigStruct + { + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index ee32be3ca7..ab04b37000 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -3,17 +3,16 @@ using System; using System.Buffers; +using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; -using SixLabors.Primitives; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Memory { public class Buffer2DTests @@ -21,37 +20,74 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, IMemoryOwner buffer, int bufferOffset = 0) + public static void SpanPointsTo(Span span, Memory buffer, int bufferOffset = 0) where T : struct { ref T actual = ref MemoryMarshal.GetReference(span); - ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset); + ref T expected = ref buffer.Span[bufferOffset]; True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + + private const int Big = 99999; + + [Theory] + [InlineData(Big, 7, 42)] + [InlineData(Big, 1025, 17)] + [InlineData(300, 42, 777)] + public unsafe void Construct(int bufferCapacity, int width, int height) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + { + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(width * height, buffer.FastMemoryGroup.TotalLength); + Assert.True(buffer.FastMemoryGroup.BufferLength % width == 0); + } + } [Theory] - [InlineData(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + [InlineData(Big, 0, 42)] + [InlineData(Big, 1, 0)] + [InlineData(60, 42, 0)] + [InlineData(3, 0, 0)] + public unsafe void Construct_Empty(int bufferCapacity, int width, int height) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.Memory.Length); + Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); + Assert.Equal(0, buffer.GetSingleSpan().Length); } } + [Theory] + [InlineData(50, 10, 20, 4)] + public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); + MemoryGroup memoryGroup = buffer.FastMemoryGroup; + int expectedAlignment = width * alignmentMultiplier; + + Assert.Equal(expectedAlignment, memoryGroup.BufferLength); + } + [Fact] public void CreateClean() { using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - Span span = buffer.GetSpan(); + Span span = buffer.GetSingleSpan(); for (int j = 0; j < span.Length; j++) { Assert.Equal(0, span[j]); @@ -60,72 +96,145 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(7, 42, 0)] - [InlineData(7, 42, 10)] - [InlineData(17, 42, 41)] - public void GetRowSpanY(int width, int height, int y) + [InlineData(Big, 7, 42, 0, 0)] + [InlineData(Big, 7, 42, 10, 0)] + [InlineData(Big, 17, 42, 41, 0)] + [InlineData(500, 17, 42, 41, 1)] + [InlineData(200, 100, 30, 1, 0)] + [InlineData(200, 100, 30, 2, 1)] + [InlineData(200, 100, 30, 4, 2)] + public unsafe void GetRowSpanY(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(y); - // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y); + + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } } + public static TheoryData GetRowSpanY_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, -1 }, + { Big, 10, 8, 8 }, + { 20, 10, 8, -1 }, + { 20, 10, 8, 10 }, + }; + [Theory] - [InlineData(7, 42, 0, 0)] - [InlineData(7, 42, 3, 10)] - [InlineData(17, 42, 0, 41)] - public void GetRowSpanXY(int width, int height, int x, int y) + [MemberData(nameof(GetRowSpanY_OutOfRange_Data))] + public void GetRowSpan_OutOfRange(int bufferCapacity, int width, int height, int y) { - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) - { - Span span = buffer.GetRowSpan(x, y); + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); - // Assert.Equal(width * y + x, span.Start); - Assert.Equal(width - x, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y + x); - } + Exception ex = Assert.ThrowsAny(() => buffer.GetRowSpan(y)); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); } + public static TheoryData Indexer_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, 1, -1 }, + { Big, 10, 8, 1, 8 }, + { Big, 10, 8, -1, 1 }, + { Big, 10, 8, 10, 1 }, + { 20, 10, 8, 1, -1 }, + { 20, 10, 8, 1, 10 }, + { 20, 10, 8, -1, 1 }, + { 20, 10, 8, 10, 1 }, + }; + [Theory] - [InlineData(42, 8, 0, 0)] - [InlineData(400, 1000, 20, 10)] - [InlineData(99, 88, 98, 87)] - public void Indexer(int width, int height, int x, int y) + [MemberData(nameof(Indexer_OutOfRange_Data))] + public void Indexer_OutOfRange(int bufferCapacity, int width, int height, int x, int y) { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); + + Exception ex = Assert.ThrowsAny(() => buffer[x, y]++); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } + + [Theory] + [InlineData(Big, 42, 8, 0, 0)] + [InlineData(Big, 400, 1000, 20, 10)] + [InlineData(Big, 99, 88, 98, 87)] + [InlineData(500, 200, 30, 42, 13)] + [InlineData(500, 200, 30, 199, 29)] + public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.MemorySource.GetSpan(); + int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); + + Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref span[y * width + x]; + ref TestStructs.Foo expected = ref span[x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } } [Fact] - public void SwapOrCopyContent() + public void SwapOrCopyContent_WhenBothAllocated() { - using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) - using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) + using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) + using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) { - IMemoryOwner aa = a.MemorySource.MemoryOwner; - IMemoryOwner bb = b.MemorySource.MemoryOwner; + a[1, 3] = 666; + b[1, 3] = 444; + + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.MemorySource.MemoryOwner); - Assert.Equal(aa, b.MemorySource.MemoryOwner); + Assert.Equal(bb, a.FastMemoryGroup.Single()); + Assert.Equal(aa, b.FastMemoryGroup.Single()); Assert.Equal(new Size(3, 7), a.Size()); Assert.Equal(new Size(10, 5), b.Size()); + + Assert.Equal(666, b[1, 3]); + Assert.Equal(444, a[1, 3]); + } + } + + [Fact] + public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() + { + using var destData = MemoryGroup.Wrap(new int[100]); + using var dest = new Buffer2D(destData, 10, 10); + + using (Buffer2D source = this.MemoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean)) + { + source[0, 0] = 1; + dest[0, 0] = 2; + + Buffer2D.SwapOrCopyContent(dest, source); } + + int actual1 = dest.GetRowSpan(0)[0]; + int actual2 = dest.GetRowSpan(0)[0]; + int actual3 = dest.GetSafeRowMemory(0).Span[0]; + int actual4 = dest.GetFastRowMemory(0).Span[0]; + int actual5 = dest[0, 0]; + + Assert.Equal(1, actual1); + Assert.Equal(1, actual2); + Assert.Equal(1, actual3); + Assert.Equal(1, actual4); + Assert.Equal(1, actual5); } [Theory] @@ -140,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - rnd.RandomFill(b.Span, 0, 1); + rnd.RandomFill(b.GetSingleSpan(), 0, 1); b.CopyColumns(startIndex, destIndex, columnCount); @@ -162,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - rnd.RandomFill(b.Span, 0, 1); + rnd.RandomFill(b.GetSingleSpan(), 0, 1); b.CopyColumns(0, 50, 22); b.CopyColumns(0, 50, 22); @@ -178,5 +287,18 @@ namespace SixLabors.ImageSharp.Tests.Memory } } } + + [Fact] + public void PublicMemoryGroup_IsMemoryGroupView() + { + using Buffer2D buffer1 = this.MemoryAllocator.Allocate2D(10, 10); + using Buffer2D buffer2 = this.MemoryAllocator.Allocate2D(10, 10); + IMemoryGroup mgBefore = buffer1.MemoryGroup; + + Buffer2D.SwapOrCopyContent(buffer1, buffer2); + + Assert.False(mgBefore.IsValid); + Assert.NotSame(mgBefore, buffer1.MemoryGroup); + } } } diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 9192798628..77e899a4c2 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; using Xunit; // ReSharper disable InconsistentNaming @@ -10,27 +9,27 @@ namespace SixLabors.ImageSharp.Tests.Memory { public class BufferAreaTests { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + [Fact] public void Construct() { - using (var buffer = Configuration.Default.MemoryAllocator.Allocate2D(10, 20)) - { - var rectangle = new Rectangle(3, 2, 5, 6); - var area = new BufferArea(buffer, rectangle); + using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); + var rectangle = new Rectangle(3, 2, 5, 6); + var area = new BufferArea(buffer, rectangle); - Assert.Equal(buffer, area.DestinationBuffer); - Assert.Equal(rectangle, area.Rectangle); - } + Assert.Equal(buffer, area.DestinationBuffer); + Assert.Equal(rectangle, area.Rectangle); } - private static Buffer2D CreateTestBuffer(int w, int h) + private Buffer2D CreateTestBuffer(int w, int h) { - var buffer = Configuration.Default.MemoryAllocator.Allocate2D(w, h); + Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - buffer[x, y] = y * 100 + x; + buffer[x, y] = (y * 100) + x; } } @@ -38,110 +37,122 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(2, 3, 2, 2)] - [InlineData(5, 4, 3, 2)] - public void Indexer(int rx, int ry, int x, int y) + [InlineData(1000, 2, 3, 2, 2)] + [InlineData(1000, 5, 4, 3, 2)] + [InlineData(200, 2, 3, 2, 2)] + [InlineData(200, 5, 4, 3, 2)] + public void Indexer(int bufferCapacity, int rx, int ry, int x, int y) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - var r = new Rectangle(rx, ry, 5, 6); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, 5, 6); - BufferArea area = buffer.GetArea(r); + BufferArea area = buffer.GetArea(r); - int value = area[x, y]; - int expected = (ry + y) * 100 + rx + x; - Assert.Equal(expected, value); - } + int value = area[x, y]; + int expected = ((ry + y) * 100) + rx + x; + Assert.Equal(expected, value); } [Theory] - [InlineData(2, 3, 2, 5, 6)] - [InlineData(5, 4, 3, 6, 5)] - public void GetRowSpan(int rx, int ry, int y, int w, int h) + [InlineData(1000, 2, 3, 2, 5, 6)] + [InlineData(1000, 5, 4, 3, 6, 5)] + [InlineData(200, 2, 3, 2, 5, 6)] + [InlineData(200, 5, 4, 3, 6, 5)] + public void GetRowSpan(int bufferCapacity, int rx, int ry, int y, int w, int h) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - var r = new Rectangle(rx, ry, w, h); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - BufferArea area = buffer.GetArea(r); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, w, h); - Span span = area.GetRowSpan(y); + BufferArea area = buffer.GetArea(r); - Assert.Equal(w, span.Length); + Span span = area.GetRowSpan(y); - for (int i = 0; i < w; i++) - { - int expected = (ry + y) * 100 + rx + i; - int value = span[i]; + Assert.Equal(w, span.Length); - Assert.Equal(expected, value); - } + for (int i = 0; i < w; i++) + { + int expected = ((ry + y) * 100) + rx + i; + int value = span[i]; + + Assert.Equal(expected, value); } } [Fact] public void GetSubArea() { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); + BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); - var expectedRect = new Rectangle(10, 12, 5, 5); + var expectedRect = new Rectangle(10, 12, 5, 5); - Assert.Equal(buffer, area1.DestinationBuffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(buffer, area1.DestinationBuffer); + Assert.Equal(expectedRect, area1.Rectangle); - int value00 = 12 * 100 + 10; - Assert.Equal(value00, area1[0, 0]); - } + int value00 = (12 * 100) + 10; + Assert.Equal(value00, area1[0, 0]); } - [Fact] - public void DangerousGetPinnableReference() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void GetReferenceToOrigin(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - ref int r = ref area0.GetReferenceToOrigin(); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - int expected = buffer[6, 8]; - Assert.Equal(expected, r); - } + ref int r = ref area0.GetReferenceToOrigin(); + + int expected = buffer[6, 8]; + Assert.Equal(expected, r); } - [Fact] - public void Clear_FullArea() + [Theory] + [InlineData(1000)] + [InlineData(70)] + public void Clear_FullArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(22, 13)) + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.CreateTestBuffer(22, 13); + var emptyRow = new int[22]; + buffer.GetArea().Clear(); + + for (int y = 0; y < 13; y++) { - buffer.GetArea().Clear(); - Span fullSpan = buffer.GetSpan(); - Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + Span row = buffer.GetRowSpan(y); + Assert.True(row.SequenceEqual(emptyRow)); } } - [Fact] - public void Clear_SubArea() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void Clear_SubArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area = buffer.GetArea(5, 5, 10, 10); - area.Clear(); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - Assert.NotEqual(0, buffer[4, 4]); - Assert.NotEqual(0, buffer[15, 15]); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area = buffer.GetArea(5, 5, 10, 10); + area.Clear(); - Assert.Equal(0, buffer[5, 5]); - Assert.Equal(0, buffer[14, 14]); + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); - for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) - { - Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); - Assert.True(span.SequenceEqual(new int[area.Width])); - } + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); + + for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) + { + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Assert.True(span.SequenceEqual(new int[area.Width])); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs new file mode 100644 index 0000000000..555d641c7b --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public struct MemoryGroupIndex : IEquatable + { + public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other); + + public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex); + + public int BufferLength { get; } + + public int BufferIndex { get; } + + public int ElementIndex { get; } + + public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex) + { + this.BufferLength = bufferLength; + this.BufferIndex = bufferIndex; + this.ElementIndex = elementIndex; + } + + public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val) + { + int nextElementIndex = idx.ElementIndex + val; + return new MemoryGroupIndex( + idx.BufferLength, + idx.BufferIndex + (nextElementIndex / idx.BufferLength), + nextElementIndex % idx.BufferLength); + } + + public bool Equals(MemoryGroupIndex other) + { + if (this.BufferLength != other.BufferLength) + { + throw new InvalidOperationException(); + } + + return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex; + } + + public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b); + + public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b); + + public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b) + { + if (a.BufferLength != b.BufferLength) + { + throw new InvalidOperationException(); + } + + if (a.BufferIndex < b.BufferIndex) + { + return true; + } + + if (a.BufferIndex == b.BufferIndex) + { + return a.ElementIndex < b.ElementIndex; + } + + return false; + } + + public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) + { + if (a.BufferLength != b.BufferLength) + { + throw new InvalidOperationException(); + } + + if (a.BufferIndex > b.BufferIndex) + { + return true; + } + + if (a.BufferIndex == b.BufferIndex) + { + return a.ElementIndex > b.ElementIndex; + } + + return false; + } + } + + internal static class MemoryGroupIndexExtensions + { + public static T GetElementAt(this IMemoryGroup group, MemoryGroupIndex idx) + where T : struct + { + return group[idx.BufferIndex].Span[idx.ElementIndex]; + } + + public static void SetElementAt(this IMemoryGroup group, MemoryGroupIndex idx, T value) + where T : struct + { + group[idx.BufferIndex].Span[idx.ElementIndex] = value; + } + + public static MemoryGroupIndex MinIndex(this IMemoryGroup group) + where T : struct + { + return new MemoryGroupIndex(group.BufferLength, 0, 0); + } + + public static MemoryGroupIndex MaxIndex(this IMemoryGroup group) + where T : struct + { + return group.Count == 0 + ? new MemoryGroupIndex(group.BufferLength, 0, 0) + : new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[group.Count - 1].Length); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs new file mode 100644 index 0000000000..f0cc18f29f --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public class MemoryGroupIndexTests + { + [Fact] + public void Equal() + { + var a = new MemoryGroupIndex(10, 1, 3); + var b = new MemoryGroupIndex(10, 1, 3); + + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + Assert.False(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerBufferIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 5, 3); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerElementIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 3, 9); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void Increment() + { + var a = new MemoryGroupIndex(10, 3, 3); + a += 1; + Assert.Equal(new MemoryGroupIndex(10, 3, 4), a); + } + + [Fact] + public void Increment_OverflowBuffer() + { + var a = new MemoryGroupIndex(10, 5, 3); + var b = new MemoryGroupIndex(10, 5, 9); + a += 8; + b += 1; + + Assert.Equal(new MemoryGroupIndex(10, 6, 1), a); + Assert.Equal(new MemoryGroupIndex(10, 6, 0), b); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs new file mode 100644 index 0000000000..298b5a93f5 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class Allocate : MemoryGroupTestsBase + { +#pragma warning disable SA1509 + public static TheoryData AllocateData = + new TheoryData() + { + { default(S5), 22, 4, 4, 1, 4, 4 }, + { default(S5), 22, 4, 7, 2, 4, 3 }, + { default(S5), 22, 4, 8, 2, 4, 4 }, + { default(S5), 22, 4, 21, 6, 4, 1 }, + + // empty: + { default(S5), 22, 0, 0, 1, -1, 0 }, + { default(S5), 22, 4, 0, 1, -1, 0 }, + + { default(S4), 50, 12, 12, 1, 12, 12 }, + { default(S4), 50, 7, 12, 2, 7, 5 }, + { default(S4), 50, 6, 12, 1, 12, 12 }, + { default(S4), 50, 5, 12, 2, 10, 2 }, + { default(S4), 50, 4, 12, 1, 12, 12 }, + { default(S4), 50, 3, 12, 1, 12, 12 }, + { default(S4), 50, 2, 12, 1, 12, 12 }, + { default(S4), 50, 1, 12, 1, 12, 12 }, + + { default(S4), 50, 12, 13, 2, 12, 1 }, + { default(S4), 50, 7, 21, 3, 7, 7 }, + { default(S4), 50, 7, 23, 4, 7, 2 }, + { default(S4), 50, 6, 13, 2, 12, 1 }, + + { default(short), 200, 50, 49, 1, 49, 49 }, + { default(short), 200, 50, 1, 1, 1, 1 }, + { default(byte), 1000, 512, 2047, 4, 512, 511 } + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + + // Act: + using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); + + // Assert: + Assert.Equal(expectedNumberOfBuffers, g.Count); + + if (expectedBufferSize >= 0) + { + Assert.Equal(expectedBufferSize, g.BufferLength); + } + + if (g.Count == 0) + { + return; + } + + for (int i = 0; i < g.Count - 1; i++) + { + Assert.Equal(g[i].Length, expectedBufferSize); + } + + Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); + } + + [Fact] + public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + { + this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 + + Assert.Throws(() => + { + MemoryGroup.Allocate(this.MemoryAllocator, 50, 43); + }); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) + { + this.MemoryAllocator.BufferCapacityInBytes = 200; + + HashSet bufferHashes; + + int expectedBlockCount = 5; + using (var g = MemoryGroup.Allocate(this.MemoryAllocator, 500, 100, allocationOptions)) + { + IReadOnlyList allocationLog = this.MemoryAllocator.AllocationLog; + Assert.Equal(expectedBlockCount, allocationLog.Count); + bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); + Assert.Equal(expectedBlockCount, bufferHashes.Count); + Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count); + + for (int i = 0; i < expectedBlockCount; i++) + { + Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); + Assert.Equal(100, allocationLog[i].Length); + Assert.Equal(200, allocationLog[i].LengthInBytes); + } + } + + Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); + Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs new file mode 100644 index 0000000000..ab69a30770 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class CopyTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.CopyTo(trg); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.CopyTo(trg)); + } + + [Theory] + [InlineData(30, 10, 40)] + [InlineData(42, 23, 42)] + [InlineData(1, 3, 10)] + [InlineData(0, 4, 0)] + public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) + { + using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); + var trg = new int[spanLength]; + src.CopyTo(trg); + + int expected = 1; + foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) + { + Assert.Equal(expected, val); + expected++; + } + } + + [Theory] + [InlineData(20, 7, 19)] + [InlineData(2, 1, 1)] + public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) + { + using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); + var trg = new int[spanLength]; + Assert.ThrowsAny(() => src.CopyTo(trg)); + } + + [Theory] + [InlineData(30, 35, 10)] + [InlineData(42, 23, 42)] + [InlineData(10, 3, 1)] + [InlineData(0, 3, 0)] + public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) + { + var src = new int[spanLength]; + for (int i = 0; i < src.Length; i++) + { + src[i] = i + 1; + } + + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); + src.AsSpan().CopyTo(trg); + + int position = 0; + for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) + { + int expected = position + 1; + Assert.Equal(expected, trg.GetElementAt(i)); + } + } + + [Theory] + [InlineData(10, 3, 11)] + [InlineData(0, 3, 1)] + public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) + { + var src = new int[spanLength]; + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); + Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs new file mode 100644 index 0000000000..c10fdc15de --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class SwapOrCopyContent : MemoryGroupTestsBase + { + [Fact] + public void WhenBothAreMemoryOwners_ShouldSwap() + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; + using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 50); + using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 50); + + Memory a0 = a[0]; + Memory a1 = a[1]; + Memory b0 = b[0]; + Memory b1 = b[1]; + + bool swap = MemoryGroup.SwapOrCopyContent(a, b); + + Assert.True(swap); + Assert.Equal(b0, a[0]); + Assert.Equal(b1, a[1]); + Assert.Equal(a0, b[0]); + Assert.Equal(a1, b[1]); + Assert.NotEqual(a[0], b[0]); + } + + [Fact] + public void WhenBothAreMemoryOwners_ShouldReplaceViews() + { + using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 100); + using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 100); + + a[0].Span[42] = 1; + b[0].Span[33] = 2; + MemoryGroupView aView0 = a.View; + MemoryGroupView bView0 = b.View; + + MemoryGroup.SwapOrCopyContent(a, b); + Assert.False(aView0.IsValid); + Assert.False(bView0.IsValid); + Assert.ThrowsAny(() => _ = aView0[0].Span); + Assert.ThrowsAny(() => _ = bView0[0].Span); + + Assert.True(a.View.IsValid); + Assert.True(b.View.IsValid); + Assert.Equal(2, a.View[0].Span[33]); + Assert.Equal(1, b.View[0].Span[42]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + using var dest = MemoryGroup.Wrap(destOwner.Memory); + + using MemoryGroup source = this.MemoryAllocator.AllocateGroup(21, 30); + + source[0].Span[10] = color; + + // Act: + bool swap = MemoryGroup.SwapOrCopyContent(dest, source); + + // Assert: + Assert.False(swap); + Assert.Equal(color, dest[0].Span[10]); + Assert.NotEqual(source[0], dest[0]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + var dest = MemoryGroup.Wrap(destOwner.Memory); + + using MemoryGroup source = this.MemoryAllocator.AllocateGroup(22, 30); + + source[0].Span[10] = color; + + // Act: + Assert.ThrowsAny(() => MemoryGroup.SwapOrCopyContent(dest, source)); + + Assert.Equal(color, source[0].Span[10]); + Assert.NotEqual(color, dest[0].Span[10]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs new file mode 100644 index 0000000000..8884037a56 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class View : MemoryGroupTestsBase + { + [Fact] + public void RefersToOwnerGroupContent() + { + using MemoryGroup group = this.CreateTestGroup(240, 80, true); + + MemoryGroupView view = group.View; + Assert.True(view.IsValid); + Assert.Equal(group.Count, view.Count); + Assert.Equal(group.BufferLength, view.BufferLength); + Assert.Equal(group.TotalLength, view.TotalLength); + int cnt = 1; + foreach (Memory memory in view) + { + Span span = memory.Span; + foreach (int t in span) + { + Assert.Equal(cnt, t); + cnt++; + } + } + } + + [Fact] + public void IsInvalidatedOnOwnerGroupDispose() + { + MemoryGroupView view; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) + { + view = group.View; + } + + Assert.False(view.IsValid); + + Assert.ThrowsAny(() => + { + _ = view.Count; + }); + + Assert.ThrowsAny(() => + { + _ = view.BufferLength; + }); + + Assert.ThrowsAny(() => + { + _ = view.TotalLength; + }); + + Assert.ThrowsAny(() => + { + _ = view[0]; + }); + } + + [Fact] + public void WhenInvalid_CanNotUseMemberMemory() + { + Memory memory; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) + { + memory = group.View[0]; + } + + Assert.ThrowsAny(() => + { + _ = memory.Span; + }); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs new file mode 100644 index 0000000000..15b07265f6 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests : MemoryGroupTestsBase + { + [Fact] + public void IsValid_TrueAfterCreation() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + + Assert.True(g.IsValid); + } + + [Fact] + public void IsValid_FalseAfterDisposal() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + + g.Dispose(); + Assert.False(g.IsValid); + } + +#pragma warning disable SA1509 + private static readonly TheoryData CopyAndTransformData = + new TheoryData() + { + { 20, 10, 20, 10 }, + { 20, 5, 20, 4 }, + { 20, 4, 20, 5 }, + { 18, 6, 20, 5 }, + { 19, 10, 20, 10 }, + { 21, 10, 22, 2 }, + { 1, 5, 5, 4 }, + + { 30, 12, 40, 5 }, + { 30, 5, 40, 12 }, + }; + + public class TransformTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.TransformTo(trg, MultiplyAllBy2); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); + } + } + + [Theory] + [InlineData(100, 5)] + [InlineData(100, 101)] + public void TransformInplace(int totalLength, int bufferLength) + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + + src.TransformInplace(s => MultiplyAllBy2(s, s)); + + int cnt = 1; + for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) + { + int val = src.GetElementAt(i); + Assert.Equal(expected: cnt * 2, val); + cnt++; + } + } + + [Fact] + public void Wrap() + { + int[] data0 = { 1, 2, 3, 4 }; + int[] data1 = { 5, 6, 7, 8 }; + int[] data2 = { 9, 10 }; + using var mgr0 = new TestMemoryManager(data0); + using var mgr1 = new TestMemoryManager(data1); + + using var group = MemoryGroup.Wrap(mgr0.Memory, mgr1.Memory, data2); + + Assert.Equal(3, group.Count); + Assert.Equal(4, group.BufferLength); + Assert.Equal(10, group.TotalLength); + + Assert.True(group[0].Span.SequenceEqual(data0)); + Assert.True(group[1].Span.SequenceEqual(data1)); + Assert.True(group[2].Span.SequenceEqual(data2)); + } + + public static TheoryData GetBoundedSlice_SuccessData = new TheoryData() + { + { 300, 100, 110, 80 }, + { 300, 100, 100, 100 }, + { 280, 100, 201, 79 }, + { 42, 7, 0, 0 }, + { 42, 7, 0, 1 }, + { 42, 7, 0, 7 }, + { 42, 9, 9, 9 }, + }; + + [Theory] + [MemberData(nameof(GetBoundedSlice_SuccessData))] + public void GetBoundedSlice_WhenArgsAreCorrect(long totalLength, int bufferLength, long start, int length) + { + using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); + + Memory slice = group.GetBoundedSlice(start, length); + + Assert.Equal(length, slice.Length); + + int expected = (int)start + 1; + foreach (int val in slice.Span) + { + Assert.Equal(expected, val); + expected++; + } + } + + public static TheoryData GetBoundedSlice_ErrorData = new TheoryData() + { + { 300, 100, -1, 91 }, + { 300, 100, 110, 91 }, + { 42, 7, 0, 8 }, + { 42, 7, 1, 7 }, + { 42, 7, 1, 30 }, + }; + + [Theory] + [MemberData(nameof(GetBoundedSlice_ErrorData))] + public void GetBoundedSlice_WhenOverlapsBuffers_Throws(long totalLength, int bufferLength, long start, int length) + { + using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); + Assert.ThrowsAny(() => group.GetBoundedSlice(start, length)); + } + + [Fact] + public void FillWithFastEnumerator() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); + + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + [Fact] + public void FillWithSlowGenericEnumerator() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); + + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + IReadOnlyList> groupAsList = group; + foreach (Memory memory in groupAsList) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + [Fact] + public void FillWithSlowEnumerator() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); + + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + IEnumerable groupAsList = group; + foreach (Memory memory in groupAsList) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + [Fact] + public void Clear() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Clear(); + + var expectedRow = new int[10]; + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) + { + Assert.Equal(source.Length, target.Length); + for (int k = 0; k < source.Length; k++) + { + target[k] = source[k] * 2; + } + } + + [StructLayout(LayoutKind.Sequential, Size = 5)] + private struct S5 + { + public override string ToString() => "S5"; + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + private struct S4 + { + public override string ToString() => "S4"; + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs new file mode 100644 index 0000000000..8dd28653c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public abstract class MemoryGroupTestsBase + { + internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + /// + /// Create a group, either uninitialized or filled with incrementing numbers starting with 1. + /// + internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); + var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); + + if (!fillSequence) + { + return g; + } + + int j = 1; + for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) + { + g.SetElementAt(i, j); + j++; + } + + return g; + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs b/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs deleted file mode 100644 index 535204e8dc..0000000000 --- a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory -{ - public class MemorySourceTests - { - public class Construction - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void InitializeAsOwner(bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg, isInternalMemorySource); - - Assert.Equal(mmg, a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.Equal(isInternalMemorySource, a.HasSwappableContents); - } - - [Fact] - public void InitializeAsObserver_MemoryOwner_IsNull() - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg.Memory); - - Assert.Null(a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.False(a.HasSwappableContents); - } - } - - public class Dispose - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenOwnershipIsTransferred_ShouldDisposeMemoryOwner(bool isInternalMemorySource) - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg, isInternalMemorySource); - - bmg.Dispose(); - Assert.True(mmg.IsDisposed); - } - - [Fact] - public void WhenMemoryObserver_ShouldNotDisposeAnything() - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg.Memory); - - bmg.Dispose(); - Assert.False(mmg.IsDisposed); - } - } - - public class SwapOrCopyContent - { - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - - private MemorySource AllocateMemorySource(int length, AllocationOptions options = AllocationOptions.None) - where T : struct - { - IMemoryOwner owner = this.MemoryAllocator.Allocate(length, options); - return new MemorySource(owner, true); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() - { - MemorySource a = this.AllocateMemorySource(13); - MemorySource b = this.AllocateMemorySource(17); - - IMemoryOwner aa = a.MemoryOwner; - IMemoryOwner bb = b.MemoryOwner; - - Memory aaa = a.Memory; - Memory bbb = b.Memory; - - MemorySource.SwapOrCopyContent(ref a, ref b); - - Assert.Equal(bb, a.MemoryOwner); - Assert.Equal(aa, b.MemoryOwner); - - Assert.Equal(bbb, a.Memory); - Assert.Equal(aaa, b.Memory); - Assert.NotEqual(a.Memory, b.Memory); - } - - [Theory] - [InlineData(false, false)] - [InlineData(true, true)] - [InlineData(true, false)] - public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner, bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(21); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, isInternalMemorySource) - : new MemorySource(sourceOwner.Memory); - - sourceOwner.Memory.Span[10] = color; - - // Act: - MemorySource.SwapOrCopyContent(ref dest, ref source); - - // Assert: - Assert.Equal(color, dest.Memory.Span[10]); - Assert.NotEqual(sourceOwner, dest.MemoryOwner); - Assert.NotEqual(destOwner, source.MemoryOwner); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(22); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, true) - : new MemorySource(sourceOwner.Memory); - sourceOwner.Memory.Span[10] = color; - - // Act: - Assert.ThrowsAny( - () => MemorySource.SwapOrCopyContent(ref dest, ref source) - ); - - Assert.Equal(color, source.Memory.Span[10]); - Assert.NotEqual(color, dest.Memory.Span[10]); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/TestStructs.cs b/tests/ImageSharp.Tests/Memory/TestStructs.cs index 2c9417b117..858bb8e646 100644 --- a/tests/ImageSharp.Tests/Memory/TestStructs.cs +++ b/tests/ImageSharp.Tests/Memory/TestStructs.cs @@ -6,8 +6,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Memory { - - public static class TestStructs { public struct Foo : IEquatable @@ -29,6 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { result[i] = new Foo(i + 1, i + 1); } + return result; } @@ -39,16 +38,15 @@ namespace SixLabors.ImageSharp.Tests.Memory public override int GetHashCode() { int hashCode = -1817952719; - hashCode = hashCode * -1521134295 + base.GetHashCode(); - hashCode = hashCode * -1521134295 + this.A.GetHashCode(); - hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + hashCode = (hashCode * -1521134295) + base.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); return hashCode; } public override string ToString() => $"({this.A},{this.B})"; } - /// /// sizeof(AlignedFoo) == sizeof(long) /// @@ -80,17 +78,18 @@ namespace SixLabors.ImageSharp.Tests.Memory { result[i] = new AlignedFoo(i + 1, i + 1); } + return result; } public override int GetHashCode() { int hashCode = -1817952719; - hashCode = hashCode * -1521134295 + base.GetHashCode(); - hashCode = hashCode * -1521134295 + this.A.GetHashCode(); - hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + hashCode = (hashCode * -1521134295) + base.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); return hashCode; } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs deleted file mode 100644 index bafb117f75..0000000000 --- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Metadata; -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Tests the class. - /// - public class ImageFrameMetaDataTests - { - [Fact] - public void ConstructorImageFrameMetaData() - { - const int frameDelay = 42; - const int colorTableLength = 128; - const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; - - var metaData = new ImageFrameMetadata(); - GifFrameMetadata gifFrameMetaData = metaData.GetFormatMetadata(GifFormat.Instance); - gifFrameMetaData.FrameDelay = frameDelay; - gifFrameMetaData.ColorTableLength = colorTableLength; - gifFrameMetaData.DisposalMethod = disposalMethod; - - var clone = new ImageFrameMetadata(metaData); - GifFrameMetadata cloneGifFrameMetaData = clone.GetFormatMetadata(GifFormat.Instance); - - Assert.Equal(frameDelay, cloneGifFrameMetaData.FrameDelay); - Assert.Equal(colorTableLength, cloneGifFrameMetaData.ColorTableLength); - Assert.Equal(disposalMethod, cloneGifFrameMetaData.DisposalMethod); - } - - [Fact] - public void CloneIsDeep() - { - var metaData = new ImageFrameMetadata(); - ImageFrameMetadata clone = metaData.DeepClone(); - Assert.False(metaData.GetFormatMetadata(GifFormat.Instance).Equals(clone.GetFormatMetadata(GifFormat.Instance))); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs deleted file mode 100644 index 39135d0037..0000000000 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.MetaData -{ - /// - /// Tests the class. - /// - public class ImageMetaDataTests - { - [Fact] - public void ConstructorImageMetaData() - { - var metaData = new ImageMetadata(); - - var exifProfile = new ExifProfile(); - - metaData.ExifProfile = exifProfile; - metaData.HorizontalResolution = 4; - metaData.VerticalResolution = 2; - - ImageMetadata clone = metaData.DeepClone(); - - Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); - Assert.Equal(4, clone.HorizontalResolution); - Assert.Equal(2, clone.VerticalResolution); - } - - [Fact] - public void CloneIsDeep() - { - var metaData = new ImageMetadata - { - ExifProfile = new ExifProfile(), - HorizontalResolution = 4, - VerticalResolution = 2 - }; - - ImageMetadata clone = metaData.DeepClone(); - clone.HorizontalResolution = 2; - clone.VerticalResolution = 4; - - Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); - Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); - Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); - } - - [Fact] - public void HorizontalResolution() - { - var metaData = new ImageMetadata(); - Assert.Equal(96, metaData.HorizontalResolution); - - metaData.HorizontalResolution = 0; - Assert.Equal(96, metaData.HorizontalResolution); - - metaData.HorizontalResolution = -1; - Assert.Equal(96, metaData.HorizontalResolution); - - metaData.HorizontalResolution = 1; - Assert.Equal(1, metaData.HorizontalResolution); - } - - [Fact] - public void VerticalResolution() - { - var metaData = new ImageMetadata(); - Assert.Equal(96, metaData.VerticalResolution); - - metaData.VerticalResolution = 0; - Assert.Equal(96, metaData.VerticalResolution); - - metaData.VerticalResolution = -1; - Assert.Equal(96, metaData.VerticalResolution); - - metaData.VerticalResolution = 1; - Assert.Equal(1, metaData.VerticalResolution); - } - - [Fact] - public void SyncProfiles() - { - var exifProfile = new ExifProfile(); - exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); - exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - - using (var image = new Image(1, 1)) - { - image.Metadata.ExifProfile = exifProfile; - image.Metadata.HorizontalResolution = 400; - image.Metadata.VerticalResolution = 500; - - image.Metadata.SyncProfiles(); - - Assert.Equal(400, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(500, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - } - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs deleted file mode 100644 index 4f928e0706..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - public class ExifProfileTests - { - public enum TestImageWriteFormat - { - Jpeg, - Png - } - - private static readonly Dictionary TestProfileValues = new Dictionary - { - { ExifTag.Software, "Software" }, - { ExifTag.Copyright, "Copyright" }, - { ExifTag.Orientation, (ushort)5 }, - { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, - { ExifTag.ImageDescription, "ImageDescription" }, - { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, - { ExifTag.Model, "Model" }, - }; - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - public void Constructor(TestImageWriteFormat imageFormat) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); - - Assert.Null(image.Metadata.ExifProfile); - - image.Metadata.ExifProfile = new ExifProfile(); - image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); - - image = WriteAndRead(image, imageFormat); - - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(1, image.Metadata.ExifProfile.Values.Count()); - - ExifValue value = image.Metadata.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); - TestValue(value, "Dirk Lemstra"); - } - - [Fact] - public void ConstructorEmpty() - { - new ExifProfile((byte[])null); - new ExifProfile(new byte[] { }); - } - - [Fact] - public void ConstructorCopy() - { - Assert.Throws(() => ((ExifProfile)null).DeepClone()); - - ExifProfile profile = GetExifProfile(); - - ExifProfile clone = profile.DeepClone(); - TestProfile(clone); - - profile.SetValue(ExifTag.ColorSpace, (ushort)2); - - clone = profile.DeepClone(); - TestProfile(clone); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - public void WriteFraction(TestImageWriteFormat imageFormat) - { - using (var memStream = new MemoryStream()) - { - double exposureTime = 1.0 / 1600; - - ExifProfile profile = GetExifProfile(); - - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); - - var image = new Image(1, 1); - image.Metadata.ExifProfile = profile; - - image = WriteAndRead(image, imageFormat); - - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - ExifValue value = profile.GetValue(ExifTag.ExposureTime); - Assert.NotNull(value); - Assert.NotEqual(exposureTime, ((Rational)value.Value).ToDouble()); - - memStream.Position = 0; - profile = GetExifProfile(); - - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.Metadata.ExifProfile = profile; - - image = WriteAndRead(image, imageFormat); - - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - value = profile.GetValue(ExifTag.ExposureTime); - Assert.Equal(exposureTime, ((Rational)value.Value).ToDouble()); - } - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - public void ReadWriteInfinity(TestImageWriteFormat imageFormat) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); - - image = WriteAndReadJpeg(image); - ExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); - - image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); - - image = WriteAndRead(image, imageFormat); - value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); - - image.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); - - image = WriteAndRead(image, imageFormat); - value = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); - Assert.NotNull(value); - Assert.Equal(new Rational(double.PositiveInfinity), value.Value); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - public void SetValue(TestImageWriteFormat imageFormat) - { - var latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; - - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - - ExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.Software); - TestValue(value, "ImageSharp"); - - Assert.Throws(() => { value.WithValue(15); }); - - image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - - TestValue(value, new SignedRational(7555, 100)); - - Assert.Throws(() => { value.WithValue(75); }); - - image.Metadata.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); - - // We also need to change this value because this overrides XResolution when the image is written. - image.Metadata.HorizontalResolution = 150.0; - - value = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); - TestValue(value, new Rational(150, 1)); - - Assert.Throws(() => { value.WithValue("ImageSharp"); }); - - image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - TestValue(value, (string)null); - - image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); - TestValue(value, latitude); - - image = WriteAndRead(image, imageFormat); - - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(17, image.Metadata.ExifProfile.Values.Count()); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.Software); - TestValue(value, "ImageSharp"); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - TestValue(value, new SignedRational(75.55)); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); - TestValue(value, new Rational(150.0)); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - Assert.Null(value); - - value = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); - TestValue(value, latitude); - - image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; - - image = WriteAndRead(image, imageFormat); - - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(8, image.Metadata.ExifProfile.Values.Count()); - - Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); - - Assert.Equal(7, image.Metadata.ExifProfile.Values.Count()); - } - - [Fact] - public void Syncs() - { - var exifProfile = new ExifProfile(); - exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); - exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - - var metaData = new ImageMetadata - { - ExifProfile = exifProfile, - HorizontalResolution = 200, - VerticalResolution = 300 - }; - - metaData.HorizontalResolution = 100; - - Assert.Equal(200, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - - exifProfile.Sync(metaData); - - Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - - metaData.VerticalResolution = 150; - - Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - - exifProfile.Sync(metaData); - - Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(150, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - } - - [Fact] - public void Values() - { - ExifProfile profile = GetExifProfile(); - - TestProfile(profile); - - Image thumbnail = profile.CreateThumbnail(); - Assert.NotNull(thumbnail); - Assert.Equal(256, thumbnail.Width); - Assert.Equal(170, thumbnail.Height); - } - - [Theory] - [InlineData(ExifTag.Software)] - [InlineData(ExifTag.Copyright)] - [InlineData(ExifTag.Model)] - [InlineData(ExifTag.ImageDescription)] - public void ReadWriteLargeProfileJpg(ExifTag exifValueToChange) - { - // arrange - var junk = new StringBuilder(); - for (int i = 0; i < 65600; i++) - { - junk.Append("a"); - } - var image = new Image(100, 100); - ExifProfile expectedProfile = CreateExifProfile(); - var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); - expectedProfile.SetValue(exifValueToChange, junk.ToString()); - image.Metadata.ExifProfile = expectedProfile; - - // act - Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); - - // assert - ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; - Assert.NotNull(actualProfile); - foreach (ExifTag expectedProfileTag in expectedProfileTags) - { - ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); - ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); - Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); - } - } - - [Fact] - public void ExifTypeUndefined() - { - // This image contains an 802 byte EXIF profile - // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) - - Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); - Assert.NotNull(image); - - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - foreach (ExifValue value in profile.Values) - { - if (value.DataType == ExifDataType.Undefined) - { - Assert.Equal(4, value.NumberOfComponents); - } - } - } - - [Fact] - public void TestArrayValueWithUnspecifiedSize() - { - // This images contains array in the exif profile that has zero components. - Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); - - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - // Force parsing of the profile. - Assert.Equal(24, profile.Values.Count); - - byte[] bytes = profile.ToByteArray(); - Assert.Equal(489, bytes.Length); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) - { - // arrange - var image = new Image(1, 1); - ExifProfile expected = CreateExifProfile(); - image.Metadata.ExifProfile = expected; - - // act - Image reloadedImage = WriteAndRead(image, imageFormat); - - // assert - ExifProfile actual = reloadedImage.Metadata.ExifProfile; - Assert.NotNull(actual); - foreach (KeyValuePair expectedProfileValue in TestProfileValues) - { - ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key); - Assert.NotNull(actualProfileValue); - Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); - } - } - - [Fact] - public void ProfileToByteArray() - { - // arrange - byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; - ExifProfile expectedProfile = CreateExifProfile(); - var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); - - // act - byte[] actualBytes = expectedProfile.ToByteArray(); - var actualProfile = new ExifProfile(actualBytes); - - // assert - Assert.NotNull(actualBytes); - Assert.NotEmpty(actualBytes); - Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); - foreach (ExifTag expectedProfileTag in expectedProfileTags) - { - ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); - ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); - Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); - } - } - - private static ExifProfile CreateExifProfile() - { - var profile = new ExifProfile(); - - foreach (KeyValuePair exifProfileValue in TestProfileValues) - { - profile.SetValue(exifProfileValue.Key, exifProfileValue.Value); - } - - return profile; - } - - internal static ExifProfile GetExifProfile() - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - return profile; - } - - private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) - { - switch (imageFormat) - { - case TestImageWriteFormat.Jpeg: - return WriteAndReadJpeg(image); - case TestImageWriteFormat.Png: - return WriteAndReadPng(image); - default: - throw new ArgumentException("unexpected test image format, only Jpeg and Png are allowed"); - } - } - - private static Image WriteAndReadJpeg(Image image) - { - using (var memStream = new MemoryStream()) - { - image.SaveAsJpeg(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - } - - private static Image WriteAndReadPng(Image image) - { - using (var memStream = new MemoryStream()) - { - image.SaveAsPng(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - } - - private static void TestProfile(ExifProfile profile) - { - Assert.NotNull(profile); - - Assert.Equal(16, profile.Values.Count()); - - foreach (ExifValue value in profile.Values) - { - Assert.NotNull(value.Value); - - if (value.Tag == ExifTag.Software) - { - Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); - } - - if (value.Tag == ExifTag.XResolution) - { - Assert.Equal(new Rational(300.0), value.Value); - } - - if (value.Tag == ExifTag.PixelXDimension) - { - Assert.Equal(2338U, value.Value); - } - } - } - - private static void TestValue(ExifValue value, string expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, Rational expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, SignedRational expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, Rational[] expected) - { - Assert.NotNull(value); - - Assert.Equal(expected, (ICollection)value.Value); - } - - private static void TestValue(ExifValue value, double expected) - { - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - } - - private static void TestValue(ExifValue value, double[] expected) - { - Assert.NotNull(value); - - Assert.Equal(expected, (ICollection)value.Value); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs deleted file mode 100644 index 19ff7d269a..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - public class ExifReaderTests - { - [Fact] - public void Read_DataIsEmpty_ReturnsEmptyCollection() - { - var reader = new ExifReader(new byte[] { }); - - IList result = reader.ReadValues(); - - Assert.Equal(0, result.Count); - } - - [Fact] - public void Read_DataIsMinimal_ReturnsEmptyCollection() - { - var reader = new ExifReader(new byte[] { 69, 120, 105, 102, 0, 0 }); - - IList result = reader.ReadValues(); - - Assert.Equal(0, result.Count); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs deleted file mode 100644 index 144a6e4a33..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - public class ExifDescriptionAttributeTests - { - [Fact] - public void TestExifTag() - { - ExifProfile exifProfile = new ExifProfile(); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); - ExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("None", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Inches", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Centimeter", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("4", value.ToString()); - - exifProfile.SetValue(ExifTag.ImageWidth, 123); - value = exifProfile.GetValue(ExifTag.ImageWidth); - Assert.Equal("123", value.ToString()); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs deleted file mode 100644 index 8d786811cf..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Linq; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - public class ExifValueTests - { - private static ExifValue GetExifValue() - { - ExifProfile profile; - using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image()) - { - profile = image.Metadata.ExifProfile; - } - - Assert.NotNull(profile); - - return profile.Values.First(); - } - - [Fact] - public void IEquatable() - { - ExifValue first = GetExifValue(); - ExifValue second = GetExifValue(); - - Assert.True(first == second); - Assert.True(first.Equals(second)); - Assert.True(first.Equals((object)second)); - } - - [Fact] - public void Properties() - { - ExifValue value = GetExifValue(); - - Assert.Equal(ExifDataType.Ascii, value.DataType); - Assert.Equal(ExifTag.GPSDOP, value.Tag); - Assert.False(value.IsArray); - Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); - Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.Value); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs deleted file mode 100644 index b6fa98b61e..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderCurvesTests - { - [Theory] - [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) - { - IccDataReader reader = CreateReader(data); - - IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) - { - IccDataReader reader = CreateReader(data); - - IccResponseCurve output = reader.ReadResponseCurve(channelCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) - { - IccDataReader reader = CreateReader(data); - - IccParametricCurve output = reader.ReadParametricCurve(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) - { - IccDataReader reader = CreateReader(data); - - IccCurveSegment output = reader.ReadCurveSegment(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) - { - IccDataReader reader = CreateReader(data); - - IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) - { - IccDataReader reader = CreateReader(data); - - IccSampledCurveElement output = reader.ReadSampledCurveElement(); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs deleted file mode 100644 index 04284cb496..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderLutTests - { - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadLut8(byte[] data, IccLut expected) - { - IccDataReader reader = CreateReader(data); - - IccLut output = reader.ReadLut8(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadLut16(byte[] data, IccLut expected, int count) - { - IccDataReader reader = CreateReader(data); - - IccLut output = reader.ReadLut16(count); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs deleted file mode 100644 index 7987e94102..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderMatrixTests - { - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) - { - IccDataReader reader = CreateReader(data); - - float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) - { - IccDataReader reader = CreateReader(data); - - float[] output = reader.ReadMatrix(yCount, isSingle); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs deleted file mode 100644 index f9e5428cd4..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderMultiProcessElementTests - { - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) - { - IccDataReader reader = CreateReader(data); - - IccMultiProcessElement output = reader.ReadMultiProcessElement(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); - - IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); - - IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); - - IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs deleted file mode 100644 index 6296390a0a..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderNonPrimitivesTests - { - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadDateTime(byte[] data, DateTime expected) - { - IccDataReader reader = CreateReader(data); - - DateTime output = reader.ReadDateTime(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadVersionNumber(byte[] data, IccVersion expected) - { - IccDataReader reader = CreateReader(data); - - IccVersion output = reader.ReadVersionNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadXyzNumber(byte[] data, Vector3 expected) - { - IccDataReader reader = CreateReader(data); - - Vector3 output = reader.ReadXyzNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadProfileId(byte[] data, IccProfileId expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileId output = reader.ReadProfileId(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) - { - IccDataReader reader = CreateReader(data); - - IccPositionNumber output = reader.ReadPositionNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) - { - IccDataReader reader = CreateReader(data); - - IccResponseNumber output = reader.ReadResponseNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) - { - IccDataReader reader = CreateReader(data); - - IccNamedColor output = reader.ReadNamedColor(coordinateCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileDescription output = reader.ReadProfileDescription(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccColorantTableEntry output = reader.ReadColorantTableEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) - { - IccDataReader reader = CreateReader(data); - - IccScreeningChannel output = reader.ReadScreeningChannel(); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs deleted file mode 100644 index 0275303291..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderPrimitivesTests - { - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadAsciiString(byte[] textBytes, int length, string expected) - { - IccDataReader reader = CreateReader(textBytes); - - string output = reader.ReadAsciiString(length); - - Assert.Equal(expected, output); - } - - [Fact] - public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() - { - IccDataReader reader = CreateReader(new byte[4]); - - Assert.Throws(() => reader.ReadAsciiString(-1)); - } - - [Fact] - public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() - { - IccDataReader reader = CreateReader(new byte[4]); - - Assert.Throws(() => reader.ReadUnicodeString(-1)); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadFix16(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadFix16(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadUFix16(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadUFix16(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadU1Fix15(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadU1Fix15(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadUFix8(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadUFix8(); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs deleted file mode 100644 index dc2c5b6acc..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataReaderTagDataEntryTests - { - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Read), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadProfileSequenceIdentifierTagDataEntry( - byte[] data, - IccProfileSequenceIdentifierTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) - { - IccDataReader reader = this.CreateReader(data); - - IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) - { - IccDataReader reader = this.CreateReader(data); - - IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); - - Assert.Equal(expected, output); - } - - private IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs deleted file mode 100644 index 585bda648b..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterCurvesTests - { - [Theory] - [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteOneDimensionalCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteResponseCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteParametricCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteCurveSegment(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteFormulaCurveElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteSampledCurveElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs deleted file mode 100644 index 621673ce42..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterLutTests - { - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteClut(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteClut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteClut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteClutF32(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut8(byte[] expected, IccLut data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteLut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut16(byte[] expected, IccLut data, int count) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs deleted file mode 100644 index 15bf762e25..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - using SixLabors.ImageSharp.Primitives; - - public class IccDataWriterMatrixTests - { - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] - internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs deleted file mode 100644 index 829b556afb..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterMultiProcessElementTests - { - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMultiProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteCurveSetProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMatrixProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] - internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteClutProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs deleted file mode 100644 index ed8a10551e..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterNonPrimitivesTests - { - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteDateTime(byte[] expected, DateTime data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteDateTime(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteVersionNumber(byte[] expected, IccVersion data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteVersionNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteXyzNumber(byte[] expected, Vector3 data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteXyzNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteProfileId(byte[] expected, IccProfileId data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteProfileId(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WritePositionNumber(byte[] expected, IccPositionNumber data) - { - IccDataWriter writer = CreateWriter(); - - writer.WritePositionNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteResponseNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteNamedColor(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteProfileDescription(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteScreeningChannel(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs deleted file mode 100644 index 464a701411..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterPrimitivesTests - { - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteAsciiString(byte[] expected, string data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteAsciiString(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteAsciiString(data, length, ensureNullTerminator); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Fact] - public void WriteAsciiStringWithNullWritesEmpty() - { - IccDataWriter writer = CreateWriter(); - - int count = writer.WriteAsciiString(null); - byte[] output = writer.GetData(); - - Assert.Equal(0, count); - Assert.Equal(Array.Empty(), output); - } - - [Fact] - public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() - { - IccDataWriter writer = CreateWriter(); - - Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); - } - - [Fact] - public void WriteUnicodeStringWithNullWritesEmpty() - { - IccDataWriter writer = CreateWriter(); - - int count = writer.WriteUnicodeString(null); - byte[] output = writer.GetData(); - - Assert.Equal(0, count); - Assert.Equal(Array.Empty(), output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteFix16(byte[] expected, float data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteFix16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteUFix16(byte[] expected, float data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUFix16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteU1Fix15(byte[] expected, float data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteU1Fix15(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteUFix8(byte[] expected, float data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUFix8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs deleted file mode 100644 index 269a8d561b..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterTagDataEntryTests - { - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUnknownTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteChromaticityTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteColorantOrderTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteColorantTableTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteCurveTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteDataTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteDateTimeTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteLut16TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteLut8TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteLutAtoBTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteLutBtoATagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMeasurementTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMultiLocalizedUnicodeTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteMultiProcessElementsTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteNamedColor2TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteParametricCurveTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteProfileSequenceDescTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteProfileSequenceIdentifierTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteResponseCurveSet16TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteFix16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteSignatureTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteTextTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUFix16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUInt16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUInt32ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUInt64ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUInt8ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteViewingConditionsTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteXyzTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteTextDescriptionTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteCrdInfoTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteScreeningTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteUcrBgTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs deleted file mode 100644 index b7b446699c..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccDataWriterTests - { - [Fact] - public void WriteEmpty() - { - IccDataWriter writer = CreateWriter(); - - writer.WriteEmpty(4); - byte[] output = writer.GetData(); - - Assert.Equal(new byte[4], output); - } - - [Theory] - [InlineData(1, 4)] - [InlineData(4, 4)] - public void WritePadding(int writePosition, int expectedLength) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteEmpty(writePosition); - writer.WritePadding(); - byte[] output = writer.GetData(); - - Assert.Equal(new byte[expectedLength], output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt8(byte[] data, byte[] expected) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt16(byte[] expected, ushort[] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayInt16(byte[] expected, short[] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt32(byte[] expected, uint[] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayInt32(byte[] expected, int[] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt64(byte[] expected, ulong[] data) - { - IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs deleted file mode 100644 index b4ed52a3d9..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccReaderTests - { - [Fact] - public void ReadProfile_NoEntries() - { - IccReader reader = CreateReader(); - - IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); - - Assert.Equal(0, output.Entries.Length); - Assert.NotNull(output.Header); - - IccProfileHeader header = output.Header; - IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; - Assert.Equal(header.Class, expected.Class); - Assert.Equal(header.CmmType, expected.CmmType); - Assert.Equal(header.CreationDate, expected.CreationDate); - Assert.Equal(header.CreatorSignature, expected.CreatorSignature); - Assert.Equal(header.DataColorSpace, expected.DataColorSpace); - Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); - Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); - Assert.Equal(header.DeviceModel, expected.DeviceModel); - Assert.Equal(header.FileSignature, expected.FileSignature); - Assert.Equal(header.Flags, expected.Flags); - Assert.Equal(header.Id, expected.Id); - Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); - Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); - Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); - Assert.Equal(header.RenderingIntent, expected.RenderingIntent); - Assert.Equal(header.Size, expected.Size); - Assert.Equal(header.Version, expected.Version); - } - - [Fact] - public void ReadProfile_DuplicateEntry() - { - IccReader reader = CreateReader(); - - IccProfile output = reader.Read(IccTestDataProfiles.Profile_Random_Array); - - Assert.Equal(2, output.Entries.Length); - Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); - } - - - private IccReader CreateReader() - { - return new IccReader(); - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs deleted file mode 100644 index e66554b85b..0000000000 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Icc -{ - public class IccWriterTests - { - [Fact] - public void WriteProfile_NoEntries() - { - IccWriter writer = CreateWriter(); - - var profile = new IccProfile - { - Header = IccTestDataProfiles.Header_Random_Write - }; - byte[] output = writer.Write(profile); - - Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); - } - - [Fact] - public void WriteProfile_DuplicateEntry() - { - IccWriter writer = CreateWriter(); - - byte[] output = writer.Write(IccTestDataProfiles.Profile_Random_Val); - - Assert.Equal(IccTestDataProfiles.Profile_Random_Array, output); - } - - private IccWriter CreateWriter() - { - return new IccWriter(); - } - } -} diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs new file mode 100644 index 0000000000..746c0f3c72 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the class. + /// + public class ImageFrameMetadataTests + { + [Fact] + public void ConstructorImageFrameMetadata() + { + const int frameDelay = 42; + const int colorTableLength = 128; + const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; + + var metaData = new ImageFrameMetadata(); + GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata(); + gifFrameMetadata.FrameDelay = frameDelay; + gifFrameMetadata.ColorTableLength = colorTableLength; + gifFrameMetadata.DisposalMethod = disposalMethod; + + var clone = new ImageFrameMetadata(metaData); + GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata(); + + Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay); + Assert.Equal(colorTableLength, cloneGifFrameMetadata.ColorTableLength); + Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMethod); + } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageFrameMetadata(); + ImageFrameMetadata clone = metaData.DeepClone(); + Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs new file mode 100644 index 0000000000..60d791e91e --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata +{ + /// + /// Tests the class. + /// + public class ImageMetadataTests + { + [Fact] + public void ConstructorImageMetadata() + { + var metaData = new ImageMetadata(); + + var exifProfile = new ExifProfile(); + + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + + ImageMetadata clone = metaData.DeepClone(); + + Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); + Assert.Equal(4, clone.HorizontalResolution); + Assert.Equal(2, clone.VerticalResolution); + } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageMetadata + { + ExifProfile = new ExifProfile(), + HorizontalResolution = 4, + VerticalResolution = 2 + }; + + ImageMetadata clone = metaData.DeepClone(); + clone.HorizontalResolution = 2; + clone.VerticalResolution = 4; + + Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); + Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); + Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); + } + + [Fact] + public void HorizontalResolution() + { + var metaData = new ImageMetadata(); + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution = 0; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution = -1; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution = 1; + Assert.Equal(1, metaData.HorizontalResolution); + } + + [Fact] + public void VerticalResolution() + { + var metaData = new ImageMetadata(); + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 0; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = -1; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 1; + Assert.Equal(1, metaData.VerticalResolution); + } + + [Fact] + public void SyncProfiles() + { + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + using (var image = new Image(1, 1)) + { + image.Metadata.ExifProfile = exifProfile; + image.Metadata.HorizontalResolution = 400; + image.Metadata.VerticalResolution = 500; + + image.Metadata.SyncProfiles(); + + Assert.Equal(400, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(500, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs new file mode 100644 index 0000000000..7069b03464 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -0,0 +1,497 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifProfileTests + { + public enum TestImageWriteFormat + { + /// + /// Writes a jpg file. + /// + Jpeg, + + /// + /// Writes a png file. + /// + Png + } + + private static readonly Dictionary TestProfileValues = new Dictionary + { + { ExifTag.Software, "Software" }, + { ExifTag.Copyright, "Copyright" }, + { ExifTag.Orientation, (ushort)5 }, + { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, + { ExifTag.ImageDescription, "ImageDescription" }, + { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, + { ExifTag.Model, "Model" }, + }; + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void Constructor(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); + + Assert.Null(image.Metadata.ExifProfile); + + const string expected = "Dirk Lemstra"; + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, expected); + + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(1, image.Metadata.ExifProfile.Values.Count); + + IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.Copyright); + + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + [Fact] + public void ConstructorEmpty() + { + new ExifProfile((byte[])null); + new ExifProfile(new byte[] { }); + } + + [Fact] + public void ConstructorCopy() + { + Assert.Throws(() => ((ExifProfile)null).DeepClone()); + + ExifProfile profile = GetExifProfile(); + + ExifProfile clone = profile.DeepClone(); + TestProfile(clone); + + profile.SetValue(ExifTag.ColorSpace, (ushort)2); + + clone = profile.DeepClone(); + TestProfile(clone); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WriteFraction(TestImageWriteFormat imageFormat) + { + using (var memStream = new MemoryStream()) + { + double exposureTime = 1.0 / 1600; + + ExifProfile profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); + + var image = new Image(1, 1); + image.Metadata.ExifProfile = profile; + + image = WriteAndRead(image, imageFormat); + + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + IExifValue value = profile.GetValue(ExifTag.ExposureTime); + Assert.NotNull(value); + Assert.NotEqual(exposureTime, value.Value.ToDouble()); + + memStream.Position = 0; + profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); + image.Metadata.ExifProfile = profile; + + image = WriteAndRead(image, imageFormat); + + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + value = profile.GetValue(ExifTag.ExposureTime); + Assert.Equal(exposureTime, value.Value.ToDouble()); + + image.Dispose(); + } + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void ReadWriteInfinity(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); + + image = WriteAndReadJpeg(image); + IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); + + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); + + image = WriteAndRead(image, imageFormat); + value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); + + image.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); + + image = WriteAndRead(image, imageFormat); + IExifValue value2 = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); + Assert.NotNull(value2); + Assert.Equal(new Rational(double.PositiveInfinity), value2.Value); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void SetValue(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + + IExifValue software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); + Assert.Equal("ImageSharp", software.Value); + + // ExifString can set integer values. + Assert.True(software.TrySetValue(15)); + Assert.False(software.TrySetValue(15F)); + + image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); + + IExifValue shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + + Assert.Equal(new SignedRational(7555, 100), shutterSpeed.Value); + Assert.False(shutterSpeed.TrySetValue(75)); + + image.Metadata.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + + // We also need to change this value because this overrides XResolution when the image is written. + image.Metadata.HorizontalResolution = 150.0; + + IExifValue xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); + Assert.Equal(new Rational(150, 1), xResolution.Value); + + Assert.False(xResolution.TrySetValue("ImageSharp")); + + image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + + IExifValue referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(referenceBlackWhite.Value); + + var expectedLatitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; + image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, expectedLatitude); + + IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + Assert.Equal(expectedLatitude, latitude.Value); + + int profileCount = image.Metadata.ExifProfile.Values.Count; + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + + // Should be 3 less. + // 1 x due to setting of null "ReferenceBlackWhite" value. + // 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere + // strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) + // https://exiftool.org/TagNames/EXIF.html + Assert.Equal(profileCount - 3, image.Metadata.ExifProfile.Values.Count); + + software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); + Assert.Equal("15", software.Value); + + shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + Assert.Equal(new SignedRational(75.55), shutterSpeed.Value); + + xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); + Assert.Equal(new Rational(150.0), xResolution.Value); + + referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(referenceBlackWhite); + + latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + Assert.Equal(expectedLatitude, latitude.Value); + + image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; + + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(8, image.Metadata.ExifProfile.Values.Count); + + Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); + + Assert.Equal(7, image.Metadata.ExifProfile.Values.Count); + } + + [Fact] + public void Syncs() + { + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + var metaData = new ImageMetadata + { + ExifProfile = exifProfile, + HorizontalResolution = 200, + VerticalResolution = 300 + }; + + metaData.HorizontalResolution = 100; + + Assert.Equal(200, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); + + metaData.VerticalResolution = 150; + + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(150, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); + } + + [Fact] + public void Values() + { + ExifProfile profile = GetExifProfile(); + + TestProfile(profile); + + Image thumbnail = profile.CreateThumbnail(); + Assert.NotNull(thumbnail); + Assert.Equal(256, thumbnail.Width); + Assert.Equal(170, thumbnail.Height); + } + + [Fact] + public void ReadWriteLargeProfileJpg() + { + ExifTag[] tags = new[] { ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription }; + foreach (ExifTag tag in tags) + { + // Arrange + var junk = new StringBuilder(); + for (int i = 0; i < 65600; i++) + { + junk.Append("a"); + } + + var image = new Image(100, 100); + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + expectedProfile.SetValue(tag, junk.ToString()); + image.Metadata.ExifProfile = expectedProfile; + + // Act + Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); + + // Assert + ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; + Assert.NotNull(actualProfile); + + foreach (ExifTag expectedProfileTag in expectedProfileTags) + { + IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); + IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); + Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); + } + + IExifValue expected = expectedProfile.GetValue(tag); + IExifValue actual = actualProfile.GetValue(tag); + Assert.Equal(expected, actual); + } + } + + [Fact] + public void ExifTypeUndefined() + { + // This image contains an 802 byte EXIF profile + // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) + Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); + Assert.NotNull(image); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + foreach (ExifValue value in profile.Values) + { + if (value.DataType == ExifDataType.Undefined) + { + Assert.True(value.IsArray); + Assert.Equal(4U, 4 * ExifDataTypes.GetSize(value.DataType)); + } + } + } + + [Fact] + public void TestArrayValueWithUnspecifiedSize() + { + // This images contains array in the exif profile that has zero components. + Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + // Force parsing of the profile. + Assert.Equal(25, profile.Values.Count); + + byte[] bytes = profile.ToByteArray(); + Assert.Equal(525, bytes.Length); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) + { + // Arrange + var image = new Image(1, 1); + image.Metadata.ExifProfile = CreateExifProfile(); + + // Act + Image reloadedImage = WriteAndRead(image, imageFormat); + + // Assert + ExifProfile actual = reloadedImage.Metadata.ExifProfile; + Assert.NotNull(actual); + foreach (KeyValuePair expectedProfileValue in TestProfileValues) + { + IExifValue actualProfileValue = actual.GetValueInternal(expectedProfileValue.Key); + Assert.NotNull(actualProfileValue); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.GetValue()); + } + } + + [Fact] + public void ProfileToByteArray() + { + // Arrange + byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + + // Act + byte[] actualBytes = expectedProfile.ToByteArray(); + var actualProfile = new ExifProfile(actualBytes); + + // Assert + Assert.NotNull(actualBytes); + Assert.NotEmpty(actualBytes); + Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); + foreach (ExifTag expectedProfileTag in expectedProfileTags) + { + IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); + IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); + Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); + } + } + + private static ExifProfile CreateExifProfile() + { + var profile = new ExifProfile(); + + foreach (KeyValuePair exifProfileValue in TestProfileValues) + { + profile.SetValueInternal(exifProfileValue.Key, exifProfileValue.Value); + } + + return profile; + } + + internal static ExifProfile GetExifProfile() + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + return profile; + } + + private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) + { + switch (imageFormat) + { + case TestImageWriteFormat.Jpeg: + return WriteAndReadJpeg(image); + case TestImageWriteFormat.Png: + return WriteAndReadPng(image); + default: + throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); + } + } + + private static Image WriteAndReadJpeg(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + + private static Image WriteAndReadPng(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsPng(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + + private static void TestProfile(ExifProfile profile) + { + Assert.NotNull(profile); + + Assert.Equal(16, profile.Values.Count); + + foreach (IExifValue value in profile.Values) + { + Assert.NotNull(value.GetValue()); + } + + IExifValue software = profile.GetValue(ExifTag.Software); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", software.Value); + + IExifValue xResolution = profile.GetValue(ExifTag.XResolution); + Assert.Equal(new Rational(300.0), xResolution.Value); + + IExifValue xDimension = profile.GetValue(ExifTag.PixelXDimension); + Assert.Equal(2338U, xDimension.Value); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs new file mode 100644 index 0000000000..85c9231fa9 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifReaderTests + { + [Fact] + public void Read_DataIsEmpty_ReturnsEmptyCollection() + { + var reader = new ExifReader(Array.Empty()); + + IList result = reader.ReadValues(); + + Assert.Equal(0, result.Count); + } + + [Fact] + public void Read_DataIsMinimal_ReturnsEmptyCollection() + { + var reader = new ExifReader(new byte[] { 69, 120, 105, 102, 0, 0 }); + + IList result = reader.ReadValues(); + + Assert.Equal(0, result.Count); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs new file mode 100644 index 0000000000..64219fce02 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifTagDescriptionAttributeTests + { + [Fact] + public void TestExifTag() + { + var exifProfile = new ExifProfile(); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); + IExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("None", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Inches", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Centimeter", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("4", value.ToString()); + + exifProfile.SetValue(ExifTag.ImageWidth, 123U); + value = exifProfile.GetValue(ExifTag.ImageWidth); + Assert.Equal("123", value.ToString()); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs new file mode 100644 index 0000000000..7f52fb6cae --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifValueTests + { + private ExifProfile profile; + + public ExifValueTests() + { + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image()) + { + this.profile = image.Metadata.ExifProfile; + } + } + + private IExifValue GetExifValue() + { + Assert.NotNull(this.profile); + + return this.profile.GetValue(ExifTag.Software); + } + + [Fact] + public void IEquatable() + { + IExifValue first = this.GetExifValue(); + IExifValue second = this.GetExifValue(); + + Assert.True(first == second); + Assert.True(first.Equals(second)); + } + + [Fact] + public void Properties() + { + IExifValue value = this.GetExifValue(); + + Assert.Equal(ExifDataType.Ascii, value.DataType); + Assert.Equal(ExifTag.Software, value.Tag); + Assert.False(value.IsArray); + + const string expected = "Windows Photo Editor 10.0.10011.16384"; + Assert.Equal(expected, value.ToString()); + Assert.Equal(expected, value.Value); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs new file mode 100644 index 0000000000..495057455b --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -0,0 +1,555 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values +{ + public class ExifValuesTests + { + public static TheoryData ByteTags => new TheoryData + { + { ExifTag.FaxProfile }, + { ExifTag.ModeNumber }, + { ExifTag.GPSAltitudeRef } + }; + + public static TheoryData ByteArrayTags => new TheoryData + { + { ExifTag.ClipPath }, + { ExifTag.VersionYear }, + { ExifTag.XMP }, + { ExifTag.CFAPattern2 }, + { ExifTag.TIFFEPStandardID }, + { ExifTag.XPTitle }, + { ExifTag.XPComment }, + { ExifTag.XPAuthor }, + { ExifTag.XPKeywords }, + { ExifTag.XPSubject }, + { ExifTag.GPSVersionID }, + }; + + public static TheoryData DoubleArrayTags => new TheoryData + { + { ExifTag.PixelScale }, + { ExifTag.IntergraphMatrix }, + { ExifTag.ModelTiePoint }, + { ExifTag.ModelTransform } + }; + + public static TheoryData LongTags => new TheoryData + { + { ExifTag.SubfileType }, + { ExifTag.SubIFDOffset }, + { ExifTag.GPSIFDOffset }, + { ExifTag.T4Options }, + { ExifTag.T6Options }, + { ExifTag.XClipPathUnits }, + { ExifTag.YClipPathUnits }, + { ExifTag.ProfileType }, + { ExifTag.CodingMethods }, + { ExifTag.T82ptions }, + { ExifTag.JPEGInterchangeFormat }, + { ExifTag.JPEGInterchangeFormatLength }, + { ExifTag.MDFileTag }, + { ExifTag.StandardOutputSensitivity }, + { ExifTag.RecommendedExposureIndex }, + { ExifTag.ISOSpeed }, + { ExifTag.ISOSpeedLatitudeyyy }, + { ExifTag.ISOSpeedLatitudezzz }, + { ExifTag.FaxRecvParams }, + { ExifTag.FaxRecvTime }, + { ExifTag.ImageNumber }, + }; + + public static TheoryData LongArrayTags => new TheoryData + { + { ExifTag.FreeOffsets }, + { ExifTag.FreeByteCounts }, + { ExifTag.ColorResponseUnit }, + { ExifTag.TileOffsets }, + { ExifTag.SMinSampleValue }, + { ExifTag.SMaxSampleValue }, + { ExifTag.JPEGQTables }, + { ExifTag.JPEGDCTables }, + { ExifTag.JPEGACTables }, + { ExifTag.StripRowCounts }, + { ExifTag.IntergraphRegisters }, + { ExifTag.TimeZoneOffset } + }; + + public static TheoryData NumberTags => new TheoryData + { + { ExifTag.ImageWidth }, + { ExifTag.ImageLength }, + { ExifTag.TileWidth }, + { ExifTag.TileLength }, + { ExifTag.BadFaxLines }, + { ExifTag.ConsecutiveBadFaxLines }, + { ExifTag.PixelXDimension }, + { ExifTag.PixelYDimension } + }; + + public static TheoryData NumberArrayTags => new TheoryData + { + { ExifTag.StripOffsets }, + { ExifTag.TileByteCounts }, + { ExifTag.ImageLayer } + }; + + public static TheoryData RationalTags => new TheoryData + { + { ExifTag.XPosition }, + { ExifTag.YPosition }, + { ExifTag.XResolution }, + { ExifTag.YResolution }, + { ExifTag.BatteryLevel }, + { ExifTag.ExposureTime }, + { ExifTag.FNumber }, + { ExifTag.MDScalePixel }, + { ExifTag.CompressedBitsPerPixel }, + { ExifTag.ApertureValue }, + { ExifTag.MaxApertureValue }, + { ExifTag.SubjectDistance }, + { ExifTag.FocalLength }, + { ExifTag.FlashEnergy2 }, + { ExifTag.FocalPlaneXResolution2 }, + { ExifTag.FocalPlaneYResolution2 }, + { ExifTag.ExposureIndex2 }, + { ExifTag.Humidity }, + { ExifTag.Pressure }, + { ExifTag.Acceleration }, + { ExifTag.FlashEnergy }, + { ExifTag.FocalPlaneXResolution }, + { ExifTag.FocalPlaneYResolution }, + { ExifTag.ExposureIndex }, + { ExifTag.DigitalZoomRatio }, + { ExifTag.GPSAltitude }, + { ExifTag.GPSDOP }, + { ExifTag.GPSSpeed }, + { ExifTag.GPSTrack }, + { ExifTag.GPSImgDirection }, + { ExifTag.GPSDestBearing }, + { ExifTag.GPSDestDistance }, + }; + + public static TheoryData RationalArrayTags => new TheoryData + { + { ExifTag.WhitePoint }, + { ExifTag.PrimaryChromaticities }, + { ExifTag.YCbCrCoefficients }, + { ExifTag.ReferenceBlackWhite }, + { ExifTag.GPSLatitude }, + { ExifTag.GPSLongitude }, + { ExifTag.GPSTimestamp }, + { ExifTag.GPSDestLatitude }, + { ExifTag.GPSDestLongitude }, + { ExifTag.LensSpecification } + }; + + public static TheoryData ShortTags => new TheoryData + { + { ExifTag.OldSubfileType }, + { ExifTag.Compression }, + { ExifTag.PhotometricInterpretation }, + { ExifTag.Thresholding }, + { ExifTag.CellWidth }, + { ExifTag.CellLength }, + { ExifTag.FillOrder }, + { ExifTag.Orientation }, + { ExifTag.SamplesPerPixel }, + { ExifTag.PlanarConfiguration }, + { ExifTag.GrayResponseUnit }, + { ExifTag.ResolutionUnit }, + { ExifTag.CleanFaxData }, + { ExifTag.InkSet }, + { ExifTag.NumberOfInks }, + { ExifTag.DotRange }, + { ExifTag.Indexed }, + { ExifTag.OPIProxy }, + { ExifTag.JPEGProc }, + { ExifTag.JPEGRestartInterval }, + { ExifTag.YCbCrPositioning }, + { ExifTag.Rating }, + { ExifTag.RatingPercent }, + { ExifTag.ExposureProgram }, + { ExifTag.Interlace }, + { ExifTag.SelfTimerMode }, + { ExifTag.SensitivityType }, + { ExifTag.MeteringMode }, + { ExifTag.LightSource }, + { ExifTag.FocalPlaneResolutionUnit2 }, + { ExifTag.SensingMethod2 }, + { ExifTag.Flash }, + { ExifTag.ColorSpace }, + { ExifTag.FocalPlaneResolutionUnit }, + { ExifTag.SensingMethod }, + { ExifTag.CustomRendered }, + { ExifTag.ExposureMode }, + { ExifTag.WhiteBalance }, + { ExifTag.FocalLengthIn35mmFilm }, + { ExifTag.SceneCaptureType }, + { ExifTag.GainControl }, + { ExifTag.Contrast }, + { ExifTag.Saturation }, + { ExifTag.Sharpness }, + { ExifTag.SubjectDistanceRange }, + { ExifTag.GPSDifferential } + }; + + public static TheoryData ShortArrayTags => new TheoryData + { + { ExifTag.BitsPerSample }, + { ExifTag.MinSampleValue }, + { ExifTag.MaxSampleValue }, + { ExifTag.GrayResponseCurve }, + { ExifTag.ColorMap }, + { ExifTag.ExtraSamples }, + { ExifTag.PageNumber }, + { ExifTag.TransferFunction }, + { ExifTag.Predictor }, + { ExifTag.HalftoneHints }, + { ExifTag.SampleFormat }, + { ExifTag.TransferRange }, + { ExifTag.DefaultImageColor }, + { ExifTag.JPEGLosslessPredictors }, + { ExifTag.JPEGPointTransforms }, + { ExifTag.YCbCrSubsampling }, + { ExifTag.CFARepeatPatternDim }, + { ExifTag.IntergraphPacketData }, + { ExifTag.ISOSpeedRatings }, + { ExifTag.SubjectArea }, + { ExifTag.SubjectLocation } + }; + + public static TheoryData SignedRationalTags => new TheoryData + { + { ExifTag.ShutterSpeedValue }, + { ExifTag.BrightnessValue }, + { ExifTag.ExposureBiasValue }, + { ExifTag.AmbientTemperature }, + { ExifTag.WaterDepth }, + { ExifTag.CameraElevationAngle } + }; + + public static TheoryData SignedRationalArrayTags => new TheoryData + { + { ExifTag.Decode } + }; + + public static TheoryData StringTags => new TheoryData + { + { ExifTag.ImageDescription }, + { ExifTag.Make }, + { ExifTag.Model }, + { ExifTag.Software }, + { ExifTag.DateTime }, + { ExifTag.Artist }, + { ExifTag.HostComputer }, + { ExifTag.Copyright }, + { ExifTag.DocumentName }, + { ExifTag.PageName }, + { ExifTag.InkNames }, + { ExifTag.TargetPrinter }, + { ExifTag.ImageID }, + { ExifTag.MDLabName }, + { ExifTag.MDSampleInfo }, + { ExifTag.MDPrepDate }, + { ExifTag.MDPrepTime }, + { ExifTag.MDFileUnits }, + { ExifTag.SEMInfo }, + { ExifTag.SpectralSensitivity }, + { ExifTag.DateTimeOriginal }, + { ExifTag.DateTimeDigitized }, + { ExifTag.SubsecTime }, + { ExifTag.SubsecTimeOriginal }, + { ExifTag.SubsecTimeDigitized }, + { ExifTag.RelatedSoundFile }, + { ExifTag.FaxSubaddress }, + { ExifTag.OffsetTime }, + { ExifTag.OffsetTimeOriginal }, + { ExifTag.OffsetTimeDigitized }, + { ExifTag.SecurityClassification }, + { ExifTag.ImageHistory }, + { ExifTag.ImageUniqueID }, + { ExifTag.OwnerName }, + { ExifTag.SerialNumber }, + { ExifTag.LensMake }, + { ExifTag.LensModel }, + { ExifTag.LensSerialNumber }, + { ExifTag.GDALMetadata }, + { ExifTag.GDALNoData }, + { ExifTag.GPSLatitudeRef }, + { ExifTag.GPSLongitudeRef }, + { ExifTag.GPSSatellites }, + { ExifTag.GPSStatus }, + { ExifTag.GPSMeasureMode }, + { ExifTag.GPSSpeedRef }, + { ExifTag.GPSTrackRef }, + { ExifTag.GPSImgDirectionRef }, + { ExifTag.GPSMapDatum }, + { ExifTag.GPSDestLatitudeRef }, + { ExifTag.GPSDestLongitudeRef }, + { ExifTag.GPSDestBearingRef }, + { ExifTag.GPSDestDistanceRef }, + { ExifTag.GPSDateStamp } + }; + + public static TheoryData UndefinedTags => new TheoryData + { + { ExifTag.FileSource }, + { ExifTag.SceneType } + }; + + public static TheoryData UndefinedArrayTags => new TheoryData + { + { ExifTag.JPEGTables }, + { ExifTag.OECF }, + { ExifTag.ExifVersion }, + { ExifTag.ComponentsConfiguration }, + { ExifTag.MakerNote }, + { ExifTag.UserComment }, + { ExifTag.FlashpixVersion }, + { ExifTag.SpatialFrequencyResponse }, + { ExifTag.SpatialFrequencyResponse2 }, + { ExifTag.Noise }, + { ExifTag.CFAPattern }, + { ExifTag.DeviceSettingDescription }, + { ExifTag.ImageSourceData }, + { ExifTag.GPSProcessingMethod }, + { ExifTag.GPSAreaInformation } + }; + + [Theory] + [MemberData(nameof(ByteTags))] + public void ExifByteTests(ExifTag tag) + { + const byte expected = byte.MaxValue; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifByte)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(ByteArrayTags))] + public void ExifByteArrayTests(ExifTag tag) + { + byte[] expected = new[] { byte.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifByteArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(DoubleArrayTags))] + public void ExifDoubleArrayTests(ExifTag tag) + { + double[] expected = new[] { double.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifDoubleArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(LongTags))] + public void ExifLongTests(ExifTag tag) + { + const uint expected = uint.MaxValue; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifLong)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(LongArrayTags))] + public void ExifLongArrayTests(ExifTag tag) + { + uint[] expected = new[] { uint.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifLongArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(NumberTags))] + public void ExifNumberTests(ExifTag tag) + { + Number expected = ushort.MaxValue; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((uint)expected)); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifNumber)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(NumberArrayTags))] + public void ExifNumberArrayTests(ExifTag tag) + { + Number[] expected = new[] { new Number(uint.MaxValue) }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifNumberArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(RationalTags))] + public void ExifRationalTests(ExifTag tag) + { + var expected = new Rational(21, 42); + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(new SignedRational(expected.ToDouble()))); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifRational)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(RationalArrayTags))] + public void ExifRationalArrayTests(ExifTag tag) + { + Rational[] expected = new[] { new Rational(21, 42) }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifRationalArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(ShortTags))] + public void ExifShortTests(ExifTag tag) + { + const ushort expected = (ushort)short.MaxValue; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue((short)expected)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifShort)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(ShortArrayTags))] + public void ExifShortArrayTests(ExifTag tag) + { + ushort[] expected = new[] { ushort.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifShortArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(SignedRationalTags))] + public void ExifSignedRationalTests(ExifTag tag) + { + var expected = new SignedRational(21, 42); + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifSignedRational)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(SignedRationalArrayTags))] + public void ExifSignedRationalArrayTests(ExifTag tag) + { + SignedRational[] expected = new[] { new SignedRational(21, 42) }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifSignedRationalArray)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(StringTags))] + public void ExifStringTests(ExifTag tag) + { + const string expected = "ImageSharp"; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(0M)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifString)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(UndefinedTags))] + public void ExifUndefinedTests(ExifTag tag) + { + const byte expected = byte.MaxValue; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifByte)value; + Assert.Equal(expected, typed.Value); + } + + [Theory] + [MemberData(nameof(UndefinedArrayTags))] + public void ExifUndefinedArrayTests(ExifTag tag) + { + byte[] expected = new[] { byte.MaxValue }; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifByteArray)value; + Assert.Equal(expected, typed.Value); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs new file mode 100644 index 0000000000..4ca7f84a57 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) + { + IccDataReader reader = this.CreateReader(data); + + IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) + { + IccDataReader reader = this.CreateReader(data); + + IccResponseCurve output = reader.ReadResponseCurve(channelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) + { + IccDataReader reader = this.CreateReader(data); + + IccParametricCurve output = reader.ReadParametricCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) + { + IccDataReader reader = this.CreateReader(data); + + IccCurveSegment output = reader.ReadCurveSegment(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) + { + IccDataReader reader = this.CreateReader(data); + + IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) + { + IccDataReader reader = this.CreateReader(data); + + IccSampledCurveElement output = reader.ReadSampledCurveElement(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs new file mode 100644 index 0000000000..96c8975378 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataReader reader = this.CreateReader(data); + + IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = this.CreateReader(data); + + IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = this.CreateReader(data); + + IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = this.CreateReader(data); + + IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut8(byte[] data, IccLut expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLut output = reader.ReadLut8(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut16(byte[] data, IccLut expected, int count) + { + IccDataReader reader = this.CreateReader(data); + + IccLut output = reader.ReadLut16(count); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs new file mode 100644 index 0000000000..8245d26e01 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) + { + IccDataReader reader = this.CreateReader(data); + + float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) + { + IccDataReader reader = this.CreateReader(data); + + float[] output = reader.ReadMatrix(yCount, isSingle); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs new file mode 100644 index 0000000000..412d5fc073 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMultiProcessElement output = reader.ReadMultiProcessElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = this.CreateReader(data); + + IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = this.CreateReader(data); + + IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = this.CreateReader(data); + + IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs new file mode 100644 index 0000000000..a050f38596 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadDateTime(byte[] data, DateTime expected) + { + IccDataReader reader = this.CreateReader(data); + + DateTime output = reader.ReadDateTime(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadVersionNumber(byte[] data, IccVersion expected) + { + IccDataReader reader = this.CreateReader(data); + + IccVersion output = reader.ReadVersionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadXyzNumber(byte[] data, Vector3 expected) + { + IccDataReader reader = this.CreateReader(data); + + Vector3 output = reader.ReadXyzNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileId(byte[] data, IccProfileId expected) + { + IccDataReader reader = this.CreateReader(data); + + IccProfileId output = reader.ReadProfileId(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) + { + IccDataReader reader = this.CreateReader(data); + + IccPositionNumber output = reader.ReadPositionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) + { + IccDataReader reader = this.CreateReader(data); + + IccResponseNumber output = reader.ReadResponseNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) + { + IccDataReader reader = this.CreateReader(data); + + IccNamedColor output = reader.ReadNamedColor(coordinateCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) + { + IccDataReader reader = this.CreateReader(data); + + IccProfileDescription output = reader.ReadProfileDescription(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccColorantTableEntry output = reader.ReadColorantTableEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) + { + IccDataReader reader = this.CreateReader(data); + + IccScreeningChannel output = reader.ReadScreeningChannel(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs new file mode 100644 index 0000000000..bd9eb1ea8d --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadAsciiString(byte[] textBytes, int length, string expected) + { + IccDataReader reader = this.CreateReader(textBytes); + + string output = reader.ReadAsciiString(length); + + Assert.Equal(expected, output); + } + + [Fact] + public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() + { + IccDataReader reader = this.CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadAsciiString(-1)); + } + + [Fact] + public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() + { + IccDataReader reader = this.CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadUnicodeString(-1)); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadFix16(byte[] data, float expected) + { + IccDataReader reader = this.CreateReader(data); + + float output = reader.ReadFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix16(byte[] data, float expected) + { + IccDataReader reader = this.CreateReader(data); + + float output = reader.ReadUFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadU1Fix15(byte[] data, float expected) + { + IccDataReader reader = this.CreateReader(data); + + float output = reader.ReadU1Fix15(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix8(byte[] data, float expected) + { + IccDataReader reader = this.CreateReader(data); + + float output = reader.ReadUFix8(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs new file mode 100644 index 0000000000..a18fb1ab88 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs @@ -0,0 +1,447 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderTagDataEntryTests + { + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Read), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceIdentifierTagDataEntry( + byte[] data, + IccProfileSequenceIdentifierTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs similarity index 100% rename from tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs new file mode 100644 index 0000000000..39ebf33749 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteOneDimensionalCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteResponseCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteParametricCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteCurveSegment(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteFormulaCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteSampledCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs new file mode 100644 index 0000000000..6245d8bb6e --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs new file mode 100644 index 0000000000..15cd27b94f --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterLutTests1 + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs new file mode 100644 index 0000000000..7c301c754c --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterLutTests2 + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs new file mode 100644 index 0000000000..0873874afa --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + using SixLabors.ImageSharp; + + public class IccDataWriterMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] + internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs new file mode 100644 index 0000000000..2888958b07 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMultiProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteCurveSetProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMatrixProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClutProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs new file mode 100644 index 0000000000..c88ea3a953 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteDateTime(byte[] expected, DateTime data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteDateTime(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteVersionNumber(byte[] expected, IccVersion data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteVersionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteXyzNumber(byte[] expected, Vector3 data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteXyzNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileId(byte[] expected, IccProfileId data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteProfileId(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WritePositionNumber(byte[] expected, IccPositionNumber data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WritePositionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteResponseNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteNamedColor(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteProfileDescription(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteScreeningChannel(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs new file mode 100644 index 0000000000..20e4a71419 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiString(byte[] expected, string data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteAsciiString(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteAsciiString(data, length, ensureNullTerminator); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Fact] + public void WriteAsciiStringWithNullWritesEmpty() + { + IccDataWriter writer = this.CreateWriter(); + + int count = writer.WriteAsciiString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } + + [Fact] + public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() + { + IccDataWriter writer = this.CreateWriter(); + + Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); + } + + [Fact] + public void WriteUnicodeStringWithNullWritesEmpty() + { + IccDataWriter writer = this.CreateWriter(); + + int count = writer.WriteUnicodeString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteFix16(byte[] expected, float data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix16(byte[] expected, float data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteU1Fix15(byte[] expected, float data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteU1Fix15(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix8(byte[] expected, float data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUFix8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs new file mode 100644 index 0000000000..85e11c8560 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs @@ -0,0 +1,412 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterTagDataEntryTests + { + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUnknownTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteChromaticityTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteColorantOrderTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteColorantTableTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteDataTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteDateTimeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut8TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLutAtoBTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLutBtoATagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMeasurementTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMultiLocalizedUnicodeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteMultiProcessElementsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteNamedColor2TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteParametricCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteProfileSequenceDescTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteProfileSequenceIdentifierTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteResponseCurveSet16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteSignatureTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteTextTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUInt16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUInt32ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUInt64ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUInt8ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteViewingConditionsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteXyzTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteTextDescriptionTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteCrdInfoTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteScreeningTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteUcrBgTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs new file mode 100644 index 0000000000..7249e03aa1 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterTests + { + [Fact] + public void WriteEmpty() + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteEmpty(4); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[4], output); + } + + [Theory] + [InlineData(1, 4)] + [InlineData(4, 4)] + public void WritePadding(int writePosition, int expectedLength) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteEmpty(writePosition); + writer.WritePadding(); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[expectedLength], output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt8(byte[] data, byte[] expected) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt16(byte[] expected, ushort[] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt16(byte[] expected, short[] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt32(byte[] expected, uint[] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt32(byte[] expected, int[] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt64(byte[] expected, ulong[] data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs similarity index 100% rename from tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs new file mode 100644 index 0000000000..1502b8b30e --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccReaderTests + { + [Fact] + public void ReadProfile_NoEntries() + { + IccReader reader = this.CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); + + Assert.Equal(0, output.Entries.Length); + Assert.NotNull(output.Header); + + IccProfileHeader header = output.Header; + IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; + Assert.Equal(header.Class, expected.Class); + Assert.Equal(header.CmmType, expected.CmmType); + Assert.Equal(header.CreationDate, expected.CreationDate); + Assert.Equal(header.CreatorSignature, expected.CreatorSignature); + Assert.Equal(header.DataColorSpace, expected.DataColorSpace); + Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); + Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); + Assert.Equal(header.DeviceModel, expected.DeviceModel); + Assert.Equal(header.FileSignature, expected.FileSignature); + Assert.Equal(header.Flags, expected.Flags); + Assert.Equal(header.Id, expected.Id); + Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); + Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); + Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); + Assert.Equal(header.RenderingIntent, expected.RenderingIntent); + Assert.Equal(header.Size, expected.Size); + Assert.Equal(header.Version, expected.Version); + } + + [Fact] + public void ReadProfile_DuplicateEntry() + { + IccReader reader = this.CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Profile_Random_Array); + + Assert.Equal(2, output.Entries.Length); + Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); + } + + private IccReader CreateReader() + { + return new IccReader(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs new file mode 100644 index 0000000000..c4ac921b10 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccWriterTests + { + [Fact] + public void WriteProfile_NoEntries() + { + IccWriter writer = this.CreateWriter(); + + var profile = new IccProfile + { + Header = IccTestDataProfiles.Header_Random_Write + }; + byte[] output = writer.Write(profile); + + Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); + } + + [Fact] + public void WriteProfile_DuplicateEntry() + { + IccWriter writer = this.CreateWriter(); + + byte[] output = writer.Write(IccTestDataProfiles.Profile_Random_Val); + + Assert.Equal(IccTestDataProfiles.Profile_Random_Array, output); + } + + private IccWriter CreateWriter() + { + return new IccWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs similarity index 100% rename from tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs new file mode 100644 index 0000000000..3baea45d62 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -0,0 +1,369 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC +{ + public class IptcProfileTests + { + private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + + public static IEnumerable AllIptcTags() + { + foreach (object tag in Enum.GetValues(typeof(IptcTag))) + { + yield return new object[] { tag }; + } + } + + [Theory] + [MemberData(nameof(AllIptcTags))] + public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var value = new string('s', tag.MaxLength() + 1); + var expectedLength = tag.MaxLength(); + + // act + profile.SetValue(tag, value); + + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal(expectedLength, actual.Value.Length); + } + + [Theory] + [MemberData(nameof(AllIptcTags))] + public void IptcProfile_SetValue_WithStrictDisabled_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var value = new string('s', tag.MaxLength() + 1); + var expectedLength = value.Length; + + // act + profile.SetValue(tag, value, false); + + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal(expectedLength, actual.Value.Length); + } + + [Theory] + [InlineData(IptcTag.DigitalCreationDate)] + [InlineData(IptcTag.ExpirationDate)] + [InlineData(IptcTag.CreatedDate)] + [InlineData(IptcTag.ReferenceDate)] + [InlineData(IptcTag.ReleaseDate)] + public void IptcProfile_SetDateValue_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var datetime = new DateTimeOffset(new DateTime(1994, 3, 17)); + + // act + profile.SetDateTimeValue(tag, datetime); + + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal("19940317", actual.Value); + } + + [Theory] + [InlineData(IptcTag.CreatedTime)] + [InlineData(IptcTag.DigitalCreationTime)] + [InlineData(IptcTag.ExpirationTime)] + [InlineData(IptcTag.ReleaseTime)] + public void IptcProfile_SetTimeValue_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var dateTimeUtc = new DateTime(1994, 3, 17, 14, 15, 16, DateTimeKind.Utc); + DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTimeUtc).ToOffset(TimeSpan.FromHours(2)); + + // act + profile.SetDateTimeValue(tag, dateTimeOffset); + + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal("161516+0200", actual.Value); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] + public void ReadIptcMetadata_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(JpegDecoder)) + { + Assert.NotNull(image.Metadata.IptcProfile); + var iptcValues = image.Metadata.IptcProfile.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); + ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); + ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); + ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); + ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); + ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); + ContainsIptcValue(iptcValues, IptcTag.Source, "source"); + ContainsIptcValue(iptcValues, IptcTag.Name, "title"); + ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); + ContainsIptcValue(iptcValues, IptcTag.City, "city"); + ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); + ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); + ContainsIptcValue(iptcValues, IptcTag.Country, "country"); + ContainsIptcValue(iptcValues, IptcTag.Category, "category"); + ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); + ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); + ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)] + public void ReadApp13_WithEmptyIptc_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + Assert.Null(image.Metadata.IptcProfile); + } + + [Fact] + public void IptcProfile_ToAndFromByteArray_Works() + { + // arrange + var profile = new IptcProfile(); + var expectedCaptionWriter = "unittest"; + var expectedCaption = "test"; + profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + profile.SetValue(IptcTag.Caption, expectedCaption); + + // act + profile.UpdateData(); + byte[] profileBytes = profile.Data; + var profileFromBytes = new IptcProfile(profileBytes); + + // assert + var iptcValues = profileFromBytes.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } + + [Fact] + public void IptcProfile_CloneIsDeep() + { + // arrange + var profile = new IptcProfile(); + var captionWriter = "unittest"; + var caption = "test"; + profile.SetValue(IptcTag.CaptionWriter, captionWriter); + profile.SetValue(IptcTag.Caption, caption); + + // act + IptcProfile clone = profile.DeepClone(); + clone.SetValue(IptcTag.Caption, "changed"); + + // assert + Assert.Equal(2, clone.Values.Count()); + var cloneValues = clone.Values.ToList(); + ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter); + ContainsIptcValue(cloneValues, IptcTag.Caption, "changed"); + ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption); + } + + [Fact] + public void IptcValue_CloneIsDeep() + { + // arrange + var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true); + + // act + IptcValue clone = iptcValue.DeepClone(); + clone.Value = "changed"; + + // assert + Assert.NotEqual(iptcValue.Value, clone.Value); + } + + [Fact] + public void WritingImage_PreservesIptcProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.IptcProfile = new IptcProfile(); + var expectedCaptionWriter = "unittest"; + var expectedCaption = "test"; + image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); + + // act + Image reloadedImage = WriteAndReadJpeg(image); + + // assert + IptcProfile actual = reloadedImage.Metadata.IptcProfile; + Assert.NotNull(actual); + var iptcValues = actual.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } + + [Theory] + [InlineData(IptcTag.ObjectAttribute)] + [InlineData(IptcTag.SubjectReference)] + [InlineData(IptcTag.SupplementalCategories)] + [InlineData(IptcTag.Keywords)] + [InlineData(IptcTag.LocationCode)] + [InlineData(IptcTag.LocationName)] + [InlineData(IptcTag.ReferenceService)] + [InlineData(IptcTag.ReferenceDate)] + [InlineData(IptcTag.ReferenceNumber)] + [InlineData(IptcTag.Byline)] + [InlineData(IptcTag.BylineTitle)] + [InlineData(IptcTag.Contact)] + [InlineData(IptcTag.LocalCaption)] + [InlineData(IptcTag.CaptionWriter)] + public void IptcProfile_AddRepeatable_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var expectedValue1 = "test"; + var expectedValue2 = "another one"; + profile.SetValue(tag, expectedValue1, false); + + // act + profile.SetValue(tag, expectedValue2, false); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(2, values.Count); + ContainsIptcValue(values, tag, expectedValue1); + ContainsIptcValue(values, tag, expectedValue2); + } + + [Theory] + [InlineData(IptcTag.RecordVersion)] + [InlineData(IptcTag.ObjectType)] + [InlineData(IptcTag.Name)] + [InlineData(IptcTag.EditStatus)] + [InlineData(IptcTag.EditorialUpdate)] + [InlineData(IptcTag.Urgency)] + [InlineData(IptcTag.Category)] + [InlineData(IptcTag.FixtureIdentifier)] + [InlineData(IptcTag.ReleaseDate)] + [InlineData(IptcTag.ReleaseTime)] + [InlineData(IptcTag.ExpirationDate)] + [InlineData(IptcTag.ExpirationTime)] + [InlineData(IptcTag.SpecialInstructions)] + [InlineData(IptcTag.ActionAdvised)] + [InlineData(IptcTag.CreatedDate)] + [InlineData(IptcTag.CreatedTime)] + [InlineData(IptcTag.DigitalCreationDate)] + [InlineData(IptcTag.DigitalCreationTime)] + [InlineData(IptcTag.OriginatingProgram)] + [InlineData(IptcTag.ProgramVersion)] + [InlineData(IptcTag.ObjectCycle)] + [InlineData(IptcTag.City)] + [InlineData(IptcTag.SubLocation)] + [InlineData(IptcTag.ProvinceState)] + [InlineData(IptcTag.CountryCode)] + [InlineData(IptcTag.Country)] + [InlineData(IptcTag.OriginalTransmissionReference)] + [InlineData(IptcTag.Headline)] + [InlineData(IptcTag.Credit)] + [InlineData(IptcTag.CopyrightNotice)] + [InlineData(IptcTag.Caption)] + [InlineData(IptcTag.ImageType)] + [InlineData(IptcTag.ImageOrientation)] + public void IptcProfile_AddNoneRepeatable_DoesOverrideOldValue(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var expectedValue = "another one"; + profile.SetValue(tag, "test", false); + + // act + profile.SetValue(tag, expectedValue, false); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(1, values.Count); + ContainsIptcValue(values, tag, expectedValue); + } + + [Fact] + public void IptcProfile_RemoveByTag_RemovesAllEntrys() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); + + // act + var result = profile.RemoveValue(IptcTag.Byline); + + // assert + Assert.True(result, "removed result should be true"); + Assert.Empty(profile.Values); + } + + [Fact] + public void IptcProfile_RemoveByTagAndValue_Works() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); + + // act + var result = profile.RemoveValue(IptcTag.Byline, "test2"); + + // assert + Assert.True(result, "removed result should be true"); + ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test"); + } + + [Fact] + public void IptcProfile_GetValue_RetrievesAllEntries() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); + profile.SetValue(IptcTag.Caption, "test"); + + // act + List result = profile.GetValues(IptcTag.Byline); + + // assert + Assert.NotNull(result); + Assert.Equal(2, result.Count); + } + + private static void ContainsIptcValue(List values, IptcTag tag, string value) + { + Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}"); + Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value), false)), $"expected iptc value '{value}' was not found for tag '{tag}'"); + } + + private static Image WriteAndReadJpeg(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Numerics/RationalTests.cs b/tests/ImageSharp.Tests/Numerics/RationalTests.cs index 7cdae7f602..7b3bd86fc9 100644 --- a/tests/ImageSharp.Tests/Numerics/RationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/RationalTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - using Xunit; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs index f828cf74f5..2931ab3910 100644 --- a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; - using Xunit; namespace SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs new file mode 100644 index 0000000000..6b542badff --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class A8Tests + { + [Fact] + public void A8_Constructor() + { + // Test the limits. + Assert.Equal(byte.MinValue, new A8(0F).PackedValue); + Assert.Equal(byte.MaxValue, new A8(1F).PackedValue); + + // Test clamping. + Assert.Equal(byte.MinValue, new A8(-1234F).PackedValue); + Assert.Equal(byte.MaxValue, new A8(1234F).PackedValue); + + // Test ordering + Assert.Equal(124, new A8(124F / byte.MaxValue).PackedValue); + Assert.Equal(26, new A8(0.1F).PackedValue); + } + + [Fact] + public void A8_Equality() + { + var left = new A8(16); + var right = new A8(32); + + Assert.True(left == new A8(16)); + Assert.True(left != right); + Assert.Equal(left, (object)new A8(16)); + } + + [Fact] + public void A8_FromScaledVector4() + { + // Arrange + A8 alpha = default; + int expected = 128; + Vector4 scaled = new A8(.5F).ToScaledVector4(); + + // Act + alpha.FromScaledVector4(scaled); + byte actual = alpha.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void A8_ToScaledVector4() + { + // Arrange + var alpha = new A8(.5F); + + // Act + Vector4 actual = alpha.ToScaledVector4(); + + // Assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(.5F, actual.W, 2); + } + + [Fact] + public void A8_ToVector4() + { + // Arrange + var alpha = new A8(.5F); + + // Act + var actual = alpha.ToVector4(); + + // Assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(.5F, actual.W, 2); + } + + [Fact] + public void A8_ToRgba32() + { + var input = new A8(128); + var expected = new Rgba32(0, 0, 0, 128); + + Rgba32 actual = default; + input.ToRgba32(ref actual); + Assert.Equal(expected, actual); + } + + [Fact] + public void A8_FromBgra5551() + { + // arrange + var alpha = default(A8); + byte expected = byte.MaxValue; + + // act + alpha.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); + + // assert + Assert.Equal(expected, alpha.PackedValue); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs deleted file mode 100644 index b6e87626b6..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.PixelFormats -{ - public class Alpha8Tests - { - [Fact] - public void Alpha8_Constructor() - { - // Test the limits. - Assert.Equal(byte.MinValue, new Alpha8(0F).PackedValue); - Assert.Equal(byte.MaxValue, new Alpha8(1F).PackedValue); - - // Test clamping. - Assert.Equal(byte.MinValue, new Alpha8(-1234F).PackedValue); - Assert.Equal(byte.MaxValue, new Alpha8(1234F).PackedValue); - - // Test ordering - Assert.Equal(124, new Alpha8(124F / byte.MaxValue).PackedValue); - Assert.Equal(26, new Alpha8(0.1F).PackedValue); - } - - [Fact] - public void Alpha8_Equality() - { - var left = new Alpha8(16); - var right = new Alpha8(32); - - Assert.True(left == new Alpha8(16)); - Assert.True(left != right); - Assert.Equal(left, (object)new Alpha8(16)); - } - - [Fact] - public void Alpha8_FromScaledVector4() - { - // Arrange - Alpha8 alpha = default; - int expected = 128; - Vector4 scaled = new Alpha8(.5F).ToScaledVector4(); - - // Act - alpha.FromScaledVector4(scaled); - byte actual = alpha.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Alpha8_ToScaledVector4() - { - // Arrange - var alpha = new Alpha8(.5F); - - // Act - Vector4 actual = alpha.ToScaledVector4(); - - // Assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(.5F, actual.W, 2); - } - - [Fact] - public void Alpha8_ToVector4() - { - // Arrange - var alpha = new Alpha8(.5F); - - // Act - var actual = alpha.ToVector4(); - - // Assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(.5F, actual.W, 2); - } - - [Fact] - public void Alpha8_ToRgba32() - { - var input = new Alpha8(128); - var expected = new Rgba32(0, 0, 0, 128); - - Rgba32 actual = default; - input.ToRgba32(ref actual); - Assert.Equal(expected, actual); - } - - [Fact] - public void Alpha8_FromBgra5551() - { - // arrange - var alpha = default(Alpha8); - byte expected = byte.MaxValue; - - // act - alpha.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); - - // assert - Assert.Equal(expected, alpha.PackedValue); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index 1ccf485fe0..74b9ef1c80 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Argb32_PackedValue() { Assert.Equal(0x80001a00u, new Argb32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); - Assert.Equal((uint)0x0, new Argb32(Vector4.Zero).PackedValue); + Assert.Equal(0x0U, new Argb32(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs index 7638c5f862..1723497390 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); - + Assert.Equal(color1, color2); } @@ -77,7 +77,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.False(a.Equals((object)b)); } - [Fact] public void FromRgba32() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs index ccefa85c1e..4dbd00cbbb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -45,6 +45,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(6160, new Bgr565(0.1F, -0.3F, 0.5F).PackedValue); Assert.Equal(0x0, new Bgr565(Vector3.Zero).PackedValue); Assert.Equal(0xFFFF, new Bgr565(Vector3.One).PackedValue); + // Make sure the swizzle is correct. Assert.Equal(0xF800, new Bgr565(Vector3.UnitX).PackedValue); Assert.Equal(0x07E0, new Bgr565(Vector3.UnitY).PackedValue); @@ -191,10 +192,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgr = default(Bgr565); ushort expected = ushort.MaxValue; - + // act bgr.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - + // assert Assert.Equal(expected, bgr.PackedValue); } @@ -221,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats ushort expected = ushort.MaxValue; // act - bgr.FromGray8(new Gray8(byte.MaxValue)); + bgr.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expected, bgr.PackedValue); @@ -235,7 +236,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats ushort expected = ushort.MaxValue; // act - bgr.FromGray16(new Gray16(ushort.MaxValue)); + bgr.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expected, bgr.PackedValue); diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index 28c022709f..6ee14c0159 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - + Assert.Equal(color1, color2); } @@ -89,7 +89,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.False(x.Equals((object)y)); } - [Fact] public void FromRgba32() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs index d4c9986252..b979eebdec 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs @@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats ushort expectedPackedValue = ushort.MaxValue; // act - bgra.FromGray16(new Gray16(ushort.MaxValue)); + bgra.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expectedPackedValue, bgra.PackedValue); @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats ushort expectedPackedValue = ushort.MaxValue; // act - bgra.FromGray8(new Gray8(byte.MaxValue)); + bgra.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expectedPackedValue, bgra.PackedValue); diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index 7751d7ab93..e36d54b52e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var color2 = new Bgra5551(new Vector4(0.0f)); var color3 = new Bgra5551(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); var color4 = new Bgra5551(1.0f, 0.0f, 0.0f, 1.0f); - + Assert.Equal(color1, color2); Assert.Equal(color3, color4); } @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgra = new Bgra5551(Vector4.One); - // act + // act Vector4 actual = bgra.ToScaledVector4(); // assert @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra5551); var expected = new Bgra5551(1.0f, 0.0f, 1.0f, 1.0f); - // act + // act bgra.FromBgra5551(expected); actual.FromBgra5551(bgra); @@ -171,10 +171,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgra = default(Bgra5551); ushort expectedPackedValue = ushort.MaxValue; - + // act bgra.FromArgb32(new Argb32(255, 255, 255, 255)); - + // assert Assert.Equal(expectedPackedValue, bgra.PackedValue); } @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats ushort expectedPackedValue = ushort.MaxValue; // act - bgra.FromGray16(new Gray16(ushort.MaxValue)); + bgra.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expectedPackedValue, bgra.PackedValue); @@ -229,7 +229,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats ushort expectedPackedValue = ushort.MaxValue; // act - bgra.FromGray8(new Gray8(byte.MaxValue)); + bgra.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expectedPackedValue, bgra.PackedValue); diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index 2dff656ac8..487adc2412 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -42,9 +42,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Byte4_PackedValue() { - Assert.Equal((uint)128, new Byte4(127.5f, -12.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal((uint)0x1a7b362d, new Byte4(0x2d, 0x36, 0x7b, 0x1a).PackedValue); - Assert.Equal((uint)0x0, new Byte4(Vector4.Zero).PackedValue); + Assert.Equal(128U, new Byte4(127.5f, -12.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal(0x1a7b362dU, new Byte4(0x2d, 0x36, 0x7b, 0x1a).PackedValue); + Assert.Equal(0x0U, new Byte4(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Byte4(Vector4.One * 255).PackedValue); } @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats uint expectedPackedValue = uint.MaxValue; // act - byte4.FromGray8(new Gray8(byte.MaxValue)); + byte4.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expectedPackedValue, byte4.PackedValue); @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats uint expectedPackedValue = uint.MaxValue; // act - byte4.FromGray16(new Gray16(ushort.MaxValue)); + byte4.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expectedPackedValue, byte4.PackedValue); @@ -195,10 +195,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var byte4 = default(Byte4); uint expectedPackedValue1 = uint.MaxValue; - + // act byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - + // assert Assert.Equal(expectedPackedValue1, byte4.PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs deleted file mode 100644 index 8a0bd62c19..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.PixelFormats -{ - public class Gray16Tests - { - [Fact] - public void AreEqual() - { - var color1 = new Gray16(3000); - var color2 = new Gray16(3000); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - var color1 = new Gray16(12345); - var color2 = new Gray16(54321); - - Assert.NotEqual(color1, color2); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - [InlineData(42)] - public void Gray16_PackedValue_EqualsInput(ushort input) - => Assert.Equal(input, new Gray16(input).PackedValue); - - [Fact] - public void Gray16_FromScaledVector4() - { - // Arrange - Gray16 gray = default; - const ushort expected = 32767; - Vector4 scaled = new Gray16(expected).ToScaledVector4(); - - // Act - gray.FromScaledVector4(scaled); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void Gray16_ToScaledVector4(ushort input) - { - // Arrange - var gray = new Gray16(input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void Gray16_FromVector4() - { - // Arrange - Gray16 gray = default; - const ushort expected = 32767; - var vector = new Gray16(expected).ToVector4(); - - // Act - gray.FromVector4(vector); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void Gray16_ToVector4(ushort input) - { - // Arrange - var gray = new Gray16(input); - - // Act - var actual = gray.ToVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void Gray16_FromRgba32() - { - // Arrange - Gray16 gray = default; - const byte rgb = 128; - ushort scaledRgb = ImageMaths.UpscaleFrom8BitTo16Bit(rgb); - ushort expected = ImageMaths.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); - - // Act - gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(8100)] - public void Gray16_ToRgba32(ushort input) - { - // Arrange - ushort expected = ImageMaths.DownScaleFrom16BitTo8Bit(input); - var gray = new Gray16(input); - - // Act - Rgba32 actual = default; - gray.ToRgba32(ref actual); - - // Assert - Assert.Equal(expected, actual.R); - Assert.Equal(expected, actual.G); - Assert.Equal(expected, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void Gray16_FromBgra5551() - { - // arrange - var gray = default(Gray16); - ushort expected = ushort.MaxValue; - - // act - gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, gray.PackedValue); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs deleted file mode 100644 index d7b50ee1e2..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.PixelFormats -{ - public class Gray8Tests - { - public static readonly TheoryData LuminanceData = new TheoryData - { - 0, - 1, - 2, - 3, - 5, - 13, - 31, - 71, - 73, - 79, - 83, - 109, - 127, - 128, - 131, - 199, - 250, - 251, - 254, - 255 - }; - - [Theory] - [InlineData(0)] - [InlineData(255)] - [InlineData(10)] - [InlineData(42)] - public void Gray8_PackedValue_EqualsInput(byte input) - => Assert.Equal(input, new Gray8(input).PackedValue); - - [Fact] - public void AreEqual() - { - var color1 = new Gray8(100); - var color2 = new Gray8(100); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - var color1 = new Gray8(100); - var color2 = new Gray8(200); - - Assert.NotEqual(color1, color2); - } - - [Fact] - public void Gray8_FromScaledVector4() - { - // Arrange - Gray8 gray = default; - const byte expected = 128; - Vector4 scaled = new Gray8(expected).ToScaledVector4(); - - // Act - gray.FromScaledVector4(scaled); - byte actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Gray8_ToScaledVector4(byte input) - { - // Arrange - var gray = new Gray8(input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(1, actual.W); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Gray8_FromVector4(byte luminance) - { - // Arrange - Gray8 gray = default; - var vector = new Gray8(luminance).ToVector4(); - - // Act - gray.FromVector4(vector); - byte actual = gray.PackedValue; - - // Assert - Assert.Equal(luminance, actual); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Gray8_ToVector4(byte input) - { - // Arrange - var gray = new Gray8(input); - - // Act - var actual = gray.ToVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(1, actual.W); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Gray8_FromRgba32(byte rgb) - { - // Arrange - Gray8 gray = default; - byte expected = ImageMaths.Get8BitBT709Luminance(rgb, rgb, rgb); - - // Act - gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); - byte actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Gray8_ToRgba32(byte luminance) - { - // Arrange - var gray = new Gray8(luminance); - - // Act - Rgba32 actual = default; - gray.ToRgba32(ref actual); - - // Assert - Assert.Equal(luminance, actual.R); - Assert.Equal(luminance, actual.G); - Assert.Equal(luminance, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void Gray8_FromBgra5551() - { - // arrange - var grey = default(Gray8); - byte expected = byte.MaxValue; - - // act - grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, grey.PackedValue); - } - - public class Rgba32Compatibility - { - // ReSharper disable once MemberHidesStaticFromOuterClass - public static readonly TheoryData LuminanceData = Gray8Tests.LuminanceData; - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Gray8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) - { - var original = new Gray8(luminance); - - Rgba32 rgba = default; - original.ToRgba32(ref rgba); - - Gray8 mirror = default; - mirror.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } - - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Rgba32_ToGray8_IsInverseOf_Gray8_ToRgba32(byte luminance) - { - var original = new Gray8(luminance); - - Rgba32 rgba = default; - original.ToRgba32(ref rgba); - - Gray8 mirror = default; - mirror.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToVector4_IsRgba32Compatible(byte luminance) - { - var original = new Gray8(luminance); - - Rgba32 rgba = default; - original.ToRgba32(ref rgba); - - var gray8Vector = original.ToVector4(); - var rgbaVector = original.ToVector4(); - - Assert.Equal(gray8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void FromVector4_IsRgba32Compatible(byte luminance) - { - var original = new Gray8(luminance); - - Rgba32 rgba = default; - original.ToRgba32(ref rgba); - - Vector4 rgbaVector = original.ToVector4(); - - Gray8 mirror = default; - mirror.FromVector4(rgbaVector); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToScaledVector4_IsRgba32Compatible(byte luminance) - { - var original = new Gray8(luminance); - - Rgba32 rgba = default; - original.ToRgba32(ref rgba); - - Vector4 gray8Vector = original.ToScaledVector4(); - Vector4 rgbaVector = original.ToScaledVector4(); - - Assert.Equal(gray8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void FromScaledVector4_IsRgba32Compatible(byte luminance) - { - var original = new Gray8(luminance); - - Rgba32 rgba = default; - original.ToRgba32(ref rgba); - - Vector4 rgbaVector = original.ToScaledVector4(); - - Gray8 mirror = default; - mirror.FromScaledVector4(rgbaVector); - - Assert.Equal(original, mirror); - } - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 85a3b8b320..b1ae7fd139 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var halfSingle = new HalfSingle(-1F); - // act + // act Vector4 actual = halfSingle.ToScaledVector4(); // assert @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void HalfSingle_FromScaledVector4() { - // arrange + // arrange Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); int expected = 48128; var halfSingle = default(HalfSingle); diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index 57da5438ca..1712a6e1ee 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { // arrange var halfVector2 = default(HalfVector2); - + // act halfVector2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index ed1a0b7209..c529e1b51b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var halfVector4 = new HalfVector4(-Vector4.One); - // act + // act Vector4 actual = halfVector4.ToScaledVector4(); // assert @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); ulong expected = 13547034390470638592uL; - // act + // act halfVector4.FromScaledVector4(scaled); ulong actual = halfVector4.PackedValue; diff --git a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs new file mode 100644 index 0000000000..179ba12b2d --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class L16Tests + { + [Fact] + public void AreEqual() + { + var color1 = new L16(3000); + var color2 = new L16(3000); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new L16(12345); + var color2 = new L16(54321); + + Assert.NotEqual(color1, color2); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + [InlineData(42)] + public void L16_PackedValue_EqualsInput(ushort input) + => Assert.Equal(input, new L16(input).PackedValue); + + [Fact] + public void L16_FromScaledVector4() + { + // Arrange + L16 gray = default; + const ushort expected = 32767; + Vector4 scaled = new L16(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void L16_ToScaledVector4(ushort input) + { + // Arrange + var gray = new L16(input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void L16_FromVector4() + { + // Arrange + L16 gray = default; + const ushort expected = 32767; + var vector = new L16(expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void L16_ToVector4(ushort input) + { + // Arrange + var gray = new L16(input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void L16_FromRgba32() + { + // Arrange + L16 gray = default; + const byte rgb = 128; + ushort scaledRgb = ImageMaths.UpscaleFrom8BitTo16Bit(rgb); + ushort expected = ImageMaths.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(8100)] + public void L16_ToRgba32(ushort input) + { + // Arrange + ushort expected = ImageMaths.DownScaleFrom16BitTo8Bit(input); + var gray = new L16(input); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(expected, actual.R); + Assert.Equal(expected, actual.G); + Assert.Equal(expected, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void L16_FromBgra5551() + { + // arrange + var gray = default(L16); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.PackedValue); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs new file mode 100644 index 0000000000..f9bb084dee --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs @@ -0,0 +1,281 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class L8Tests + { + public static readonly TheoryData LuminanceData + = new TheoryData + { + 0, + 1, + 2, + 3, + 5, + 13, + 31, + 71, + 73, + 79, + 83, + 109, + 127, + 128, + 131, + 199, + 250, + 251, + 254, + 255 + }; + + [Theory] + [InlineData(0)] + [InlineData(255)] + [InlineData(10)] + [InlineData(42)] + public void L8_PackedValue_EqualsInput(byte input) + => Assert.Equal(input, new L8(input).PackedValue); + + [Fact] + public void AreEqual() + { + var color1 = new L8(100); + var color2 = new L8(100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new L8(100); + var color2 = new L8(200); + + Assert.NotEqual(color1, color2); + } + + [Fact] + public void L8_FromScaledVector4() + { + // Arrange + L8 gray = default; + const byte expected = 128; + Vector4 scaled = new L8(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToScaledVector4(byte input) + { + // Arrange + var gray = new L8(input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromVector4(byte luminance) + { + // Arrange + L8 gray = default; + var vector = new L8(luminance).ToVector4(); + + // Act + gray.FromVector4(vector); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(luminance, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToVector4(byte input) + { + // Arrange + var gray = new L8(input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromRgba32(byte rgb) + { + // Arrange + L8 gray = default; + byte expected = ImageMaths.Get8BitBT709Luminance(rgb, rgb, rgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToRgba32(byte luminance) + { + // Arrange + var gray = new L8(luminance); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(luminance, actual.R); + Assert.Equal(luminance, actual.G); + Assert.Equal(luminance, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void L8_FromBgra5551() + { + // arrange + var grey = default(L8); + byte expected = byte.MaxValue; + + // act + grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, grey.PackedValue); + } + + public class Rgba32Compatibility + { + // ReSharper disable once MemberHidesStaticFromOuterClass + public static readonly TheoryData LuminanceData = L8Tests.LuminanceData; + + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + L8 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToL8_IsInverseOf_L8_ToRgba32(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + L8 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var l8Vector = original.ToVector4(); + var rgbaVector = original.ToVector4(); + + Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var rgbaVector = original.ToVector4(); + + L8 mirror = default; + mirror.FromVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 l8Vector = original.ToScaledVector4(); + Vector4 rgbaVector = original.ToScaledVector4(); + + Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 rgbaVector = original.ToScaledVector4(); + + L8 mirror = default; + mirror.FromScaledVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs new file mode 100644 index 0000000000..3ad2dccdd2 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class La16Tests + { + public static readonly TheoryData LuminanceData + = new TheoryData + { + 0, + 1, + 2, + 3, + 5, + 13, + 31, + 71, + 73, + 79, + 83, + 109, + 127, + 128, + 131, + 199, + 250, + 251, + 254, + 255 + }; + + [Theory] + [InlineData(0, 0)] + [InlineData(255, 65535)] + [InlineData(10, 2570)] + [InlineData(42, 10794)] + public void La16_PackedValue_EqualsPackedInput(byte input, ushort packed) + => Assert.Equal(packed, new La16(input, input).PackedValue); + + [Fact] + public void AreEqual() + { + var color1 = new La16(100, 50); + var color2 = new La16(100, 50); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new La16(100, 50); + var color2 = new La16(200, 50); + + Assert.NotEqual(color1, color2); + } + + [Fact] + public void La16_FromScaledVector4() + { + // Arrange + La16 gray = default; + const ushort expected = 32896; + Vector4 scaled = new La16(128, 128).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToScaledVector4(byte input) + { + // Arrange + var gray = new La16(input, input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(scaledInput, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromVector4(byte luminance) + { + // Arrange + La16 gray = default; + var vector = new La16(luminance, luminance).ToVector4(); + + // Act + gray.FromVector4(vector); + byte actualL = gray.L; + byte actualA = gray.A; + + // Assert + Assert.Equal(luminance, actualL); + Assert.Equal(luminance, actualA); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToVector4(byte input) + { + // Arrange + var gray = new La16(input, input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(scaledInput, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromRgba32(byte rgb) + { + // Arrange + La16 gray = default; + byte expected = ImageMaths.Get8BitBT709Luminance(rgb, rgb, rgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + byte actual = gray.L; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(255, gray.A); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToRgba32(byte luminance) + { + // Arrange + var gray = new La16(luminance, luminance); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(luminance, actual.R); + Assert.Equal(luminance, actual.G); + Assert.Equal(luminance, actual.B); + Assert.Equal(luminance, actual.A); + } + + [Fact] + public void La16_FromBgra5551() + { + // arrange + var grey = default(La16); + byte expected = byte.MaxValue; + + // act + grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, grey.L); + Assert.Equal(expected, grey.A); + } + + public class Rgba32Compatibility + { + // ReSharper disable once MemberHidesStaticFromOuterClass + public static readonly TheoryData LuminanceData = La16Tests.LuminanceData; + + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromRgba32_IsInverseOf_ToRgba32(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + La16 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToLa16_IsInverseOf_La16_ToRgba32(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + La16 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var la16Vector = original.ToVector4(); + var rgbaVector = original.ToVector4(); + + Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var rgbaVector = original.ToVector4(); + + La16 mirror = default; + mirror.FromVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 la16Vector = original.ToScaledVector4(); + Vector4 rgbaVector = original.ToScaledVector4(); + + Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 rgbaVector = original.ToScaledVector4(); + + La16 mirror = default; + mirror.FromScaledVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs new file mode 100644 index 0000000000..40739c69a2 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class La32Tests + { + [Fact] + public void AreEqual() + { + var color1 = new La32(3000, 100); + var color2 = new La32(3000, 100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new La32(12345, 100); + var color2 = new La32(54321, 100); + + Assert.NotEqual(color1, color2); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(65535, 4294967295)] + [InlineData(32767, 2147450879)] + [InlineData(42, 2752554)] + public void La32_PackedValue_EqualsInput(ushort input, uint packed) + => Assert.Equal(packed, new La32(input, input).PackedValue); + + [Fact] + public void La32_FromScaledVector4() + { + // Arrange + La32 gray = default; + const ushort expected = 32767; + Vector4 scaled = new La32(expected, expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.L; + ushort actualA = gray.A; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(expected, actualA); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void La32_ToScaledVector4(ushort input) + { + // Arrange + var gray = new La32(input, input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(vectorInput, actual.W); + } + + [Fact] + public void La32_FromVector4() + { + // Arrange + La32 gray = default; + const ushort expected = 32767; + var vector = new La32(expected, expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.L; + ushort actualA = gray.A; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(expected, actualA); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void La32_ToVector4(ushort input) + { + // Arrange + var gray = new La32(input, input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(vectorInput, actual.W); + } + + [Fact] + public void La32_FromRgba32() + { + // Arrange + La32 gray = default; + const byte rgb = 128; + ushort scaledRgb = ImageMaths.UpscaleFrom8BitTo16Bit(rgb); + ushort expected = ImageMaths.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + ushort actual = gray.L; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(ushort.MaxValue, gray.A); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(8100)] + public void La32_ToRgba32(ushort input) + { + // Arrange + ushort expected = ImageMaths.DownScaleFrom16BitTo8Bit(input); + var gray = new La32(input, ushort.MaxValue); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(expected, actual.R); + Assert.Equal(expected, actual.G); + Assert.Equal(expected, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void La32_FromBgra5551() + { + // arrange + var gray = default(La32); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.L); + Assert.Equal(expected, gray.A); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 7687a7777c..0ab7033983 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -43,9 +43,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void NormalizedByte4_PackedValues() { Assert.Equal(0xA740DA0D, new NormalizedByte4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); - Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(958796544U, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); } @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); uint expected = 0x81818181; - // act + // act pixel.FromScaledVector4(scaled); uint actual = pixel.PackedValue; @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 expected = Vector4.One; // act - byte4.FromGray8(new Gray8(byte.MaxValue)); + byte4.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expected, byte4.ToScaledVector4()); @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 expected = Vector4.One; // act - byte4.FromGray16(new Gray16(ushort.MaxValue)); + byte4.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expected, byte4.ToScaledVector4()); diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index ff9350b701..a726cee4e6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -14,9 +14,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { Assert.Equal(0xE6672CCC, new NormalizedShort2(0.35f, -0.2f).PackedValue); Assert.Equal(3650751693, new NormalizedShort2(0.1f, -0.3f).PackedValue); - Assert.Equal((uint)0x0, new NormalizedShort2(Vector2.Zero).PackedValue); - Assert.Equal((uint)0x7FFF7FFF, new NormalizedShort2(Vector2.One).PackedValue); + Assert.Equal(0x0U, new NormalizedShort2(Vector2.Zero).PackedValue); + Assert.Equal(0x7FFF7FFFU, new NormalizedShort2(Vector2.One).PackedValue); Assert.Equal(0x80018001, new NormalizedShort2(-Vector2.One).PackedValue); + // TODO: I don't think this can ever pass since the bytes are already truncated. // Assert.Equal(3650751693, n.PackedValue); } @@ -34,8 +35,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void NormalizedShort2_ToVector4() { - Assert.Equal(new Vector4(1, 1, 0, 1), (new NormalizedShort2(Vector2.One)).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), (new NormalizedShort2(Vector2.Zero)).ToVector4()); + Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedShort2(Vector2.One).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedShort2(Vector2.Zero).ToVector4()); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index b872e58b68..96334be02e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -43,9 +43,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void NormalizedShort4_PackedValues() { Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(4150390751449251866UL, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); } @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); ulong expected = 0x7FFF7FFF7FFF7FFF; - // act + // act pixel.FromScaledVector4(scaled); ulong actual = pixel.PackedValue; @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 expected = Vector4.One; // act - byte4.FromGray8(new Gray8(byte.MaxValue)); + byte4.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expected, byte4.ToScaledVector4()); @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 expected = Vector4.One; // act - byte4.FromGray16(new Gray16(ushort.MaxValue)); + byte4.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expected, byte4.ToScaledVector4()); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs new file mode 100644 index 0000000000..e2d370cc0e --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class PixelBlenderTests + { + public static TheoryData BlenderMappings = new TheoryData + { + { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, + { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, + { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, + { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, + { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, + { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, + { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, + { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, + { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, + { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, + }; + + [Theory] + [MemberData(nameof(BlenderMappings))] + public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) + where TPixel : unmanaged, IPixel + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); + Assert.IsType(type, blender); + } + + public static TheoryData ColorBlendingExpectedResults = new TheoryData + { + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Normal, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, + }; + + [Theory] + [MemberData(nameof(ColorBlendingExpectedResults))] + public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); + Rgba32 actualResult = blender.Blend(backdrop, source, opacity); + + // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults + Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); + } + + public static TheoryData AlphaCompositionExpectedResults = new TheoryData + { + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue }, + }; + + [Theory] + [MemberData(nameof(AlphaCompositionExpectedResults))] + public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode); + + Rgba32 actualResult = blender.Blend(backdrop, source, opacity); + + // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults + Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 09a78a6aa1..a91ea09776 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders PixelAlphaCompositionMode.DestOut, PixelAlphaCompositionMode.Clear, PixelAlphaCompositionMode.Xor - }; + }; [Theory] [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] @@ -36,16 +36,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders using (Image src = srcFile.CreateRgba32Image()) using (Image dest = provider.GetImage()) { - GraphicsOptions options = new GraphicsOptions - { - AlphaCompositionMode = mode + var options = new GraphicsOptions + { + Antialias = false, + AlphaCompositionMode = mode }; using (Image res = dest.Clone(x => x.DrawImage(src, options))) { string combinedMode = mode.ToString(); - if (combinedMode != "Src" && combinedMode.StartsWith("Src")) combinedMode = combinedMode.Substring(3); + if (combinedMode != "Src" && combinedMode.StartsWith("Src")) + { + combinedMode = combinedMode.Substring(3); + } res.DebugSave(provider, combinedMode); res.CompareToReferenceOutput(provider, combinedMode); @@ -53,4 +57,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index e397f70b08..7831dc1242 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -10,9 +10,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { public class PorterDuffFunctionsTests { - public static TheoryData NormalBlendFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + public static TheoryData NormalBlendFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) } }; [Theory] @@ -23,15 +24,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders Assert.Equal(expected, actual); } - public static TheoryData MultiplyFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, - { - new TestVector4(0.9f,0.9f,0.9f,0.9f), - new TestVector4(0.4f,0.4f,0.4f,0.4f), - .5f, - new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) - }, + public static TheoryData MultiplyFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + { new TestVector4(0.9f, 0.9f, 0.9f, 0.9f), new TestVector4(0.4f, 0.4f, 0.4f, 0.4f), .5f, new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) } }; [Theory] @@ -42,15 +39,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData AddFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2075676f, .2075676f, .2075676f, .37f) - }, + public static TheoryData AddFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2075676f, .2075676f, .2075676f, .37f) } }; [Theory] @@ -61,15 +54,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData SubtractFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(0,0,0,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2027027f, .2027027f, .2027027f, .37f) - }, + public static TheoryData SubtractFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2027027f, .2027027f, .2027027f, .37f) } }; [Theory] @@ -80,15 +69,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData ScreenFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2383784f, .2383784f, .2383784f, .37f) - }, + public static TheoryData ScreenFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2383784f, .2383784f, .2383784f, .37f) } }; [Theory] @@ -99,15 +84,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData DarkenFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f,.6f,.6f, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2189189f, .2189189f, .2189189f, .37f) - }, + public static TheoryData DarkenFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2189189f, .2189189f, .2189189f, .37f) } }; [Theory] @@ -118,15 +99,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData LightenFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.227027f, .227027f, .227027f, .37f) - }, + public static TheoryData LightenFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.227027f, .227027f, .227027f, .37f) }, }; [Theory] @@ -137,15 +114,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData OverlayFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2124324f, .2124324f, .2124324f, .37f) - }, + public static TheoryData OverlayFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2124324f, .2124324f, .2124324f, .37f) }, }; [Theory] @@ -156,15 +129,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData HardLightFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f,0.6f,0.6f,1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2124324f, .2124324f, .2124324f, .37f) - }, + public static TheoryData HardLightFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2124324f, .2124324f, .2124324f, .37f) }, }; [Theory] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs new file mode 100644 index 0000000000..f41fbc0226 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -0,0 +1,386 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders +{ + public class PorterDuffFunctionsTestsTPixel + { + private static Span AsSpan(T value) + where T : struct + { + return new Span(new[] { value }); + } + + public static TheoryData NormalBlendFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) } + }; + + private Configuration Configuration => Configuration.Default; + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.NormalSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData MultiplyFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + { + new TestPixel(0.9f, 0.9f, 0.9f, 0.9f), + new TestPixel(0.4f, 0.4f, 0.4f, 0.4f), + .5f, + new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.MultiplySrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData AddFunctionData = new TheoryData + { + { + new TestPixel(1, 1, 1, 1), + new TestPixel(1, 1, 1, 1), + 1, + new TestPixel(1, 1, 1, 1) + }, + { + new TestPixel(1, 1, 1, 1), + new TestPixel(0, 0, 0, .8f), + .5f, + new TestPixel(1f, 1f, 1f, 1f) + }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) + } + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.AddSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData SubtractFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(0, 0, 0, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.SubtractSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData ScreenFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.ScreenSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData DarkenFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(.6f, .6f, .6f, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.DarkenSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData LightenFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.227027f, .227027f, .227027f, .37f) + } + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.LightenSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData OverlayFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + } + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.OverlaySrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + + public static TheoryData HardLightFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.HardLightSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs deleted file mode 100644 index 859be6b205..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders -{ - public class PorterDuffFunctionsTestsTPixel - { - private static Span AsSpan(T value) - where T : struct - { - return new Span(new[] { value }); - } - - public static TheoryData NormalBlendFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, - }; - - private Configuration Configuration => Configuration.Default; - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.NormalSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData MultiplyFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, - { - new TestPixel(0.9f,0.9f,0.9f,0.9f), - new TestPixel(0.4f,0.4f,0.4f,0.4f), - .5f, - new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) - }, - }; - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.MultiplySrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData AddFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1f, 1f, 1f, 1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) - }, - }; - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.AddSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData SubtractFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(0,0,0,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.2027027f, .2027027f, .2027027f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.SubtractSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData ScreenFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.2383784f, .2383784f, .2383784f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.ScreenSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData DarkenFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f,.6f,.6f, 1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.2189189f, .2189189f, .2189189f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.DarkenSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData LightenFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.227027f, .227027f, .227027f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.LightenSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData OverlayFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.2124324f, .2124324f, .2124324f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.OverlaySrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - - public static TheoryData HardLightFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f,0.6f,0.6f,1f) }, - { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestPixel(.2124324f, .2124324f, .2124324f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = PorterDuffFunctions.HardLightSrcOver((TPixel)back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back, source, amount); - VectorAssert.Equal(expected, actual, 2); - } - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected, dest[0], 2); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index d03cfeee22..2ff5157b79 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -1,11 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. - -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. - -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; @@ -53,7 +47,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - where TSourcePixel : struct, IPixel where TDestinationPixel : struct, IPixel + where TSourcePixel : unmanaged, IPixel + where TDestinationPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); @@ -69,32 +64,31 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats return; } - // Gray8 and Gray16 are special implementations of IPixel in that they do not conform to the + // L8 and L16 are special implementations of IPixel in that they do not conform to the // standard RGBA colorspace format and must be converted from RGBA using the special ITU BT709 algorithm. // One of the requirements of FromScaledVector4/ToScaledVector4 is that it unaware of this and // packs/unpacks the pixel without and conversion so we employ custom methods do do this. - if (typeof(TDestinationPixel) == typeof(Gray16)) + if (typeof(TDestinationPixel) == typeof(L16)) { - ref Gray16 gray16Ref = ref MemoryMarshal.GetReference( - MemoryMarshal.Cast(destinationPixels)); + ref L16 l16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationPixels)); for (int i = 0; i < count; i++) { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i); + ref L16 dp = ref Unsafe.Add(ref l16Ref, i); dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); } return; } - if (typeof(TDestinationPixel) == typeof(Gray8)) + if (typeof(TDestinationPixel) == typeof(L8)) { - ref Gray8 gray8Ref = ref MemoryMarshal.GetReference( - MemoryMarshal.Cast(destinationPixels)); + ref L8 l8Ref = ref MemoryMarshal.GetReference( + MemoryMarshal.Cast(destinationPixels)); for (int i = 0; i < count; i++) { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); - ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i); + ref L8 dp = ref Unsafe.Add(ref l8Ref, i); dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index f1f56fcd46..19623c3d8f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -1,4 +1,7 @@ -using SixLabors.ImageSharp.PixelFormats; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; using Xunit; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs index e98e14fc62..817c29aa1e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs @@ -1,9 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -38,8 +34,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } [Theory] - [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand, - PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)] + [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)] [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, PixelConversionModifiers.None)] internal void Remove( PixelConversionModifiers baselineModifiers, @@ -62,4 +57,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(expected, result); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs index c881ae96ba..9a0f4d8acb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +12,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public class Argb32OperationsTests : PixelOperationsTests { - public Argb32OperationsTests(ITestOutputHelper output) : base(output) { @@ -22,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs deleted file mode 100644 index ffb1a0b027..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations -{ - public partial class PixelOperationsTests - { - public class Gray16OperationsTests : PixelOperationsTests - { - public Gray16OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs deleted file mode 100644 index 24b20eecab..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations -{ - public partial class PixelOperationsTests - { - public class Gray8OperationsTests : PixelOperationsTests - { - public Gray8OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs new file mode 100644 index 0000000000..87aed91e76 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class L16OperationsTests : PixelOperationsTests + { + public L16OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L8OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L8OperationsTests.cs new file mode 100644 index 0000000000..b2a1a2dc3a --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L8OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class L8OperationsTests : PixelOperationsTests + { + public L8OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs new file mode 100644 index 0000000000..a17594af6a --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class La16OperationsTests : PixelOperationsTests + { + public La16OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs new file mode 100644 index 0000000000..dce9342866 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class La32OperationsTests : PixelOperationsTests + { + public La32OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 682da89fcc..9d48675f16 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -18,14 +18,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public partial class PixelOperationsTests { +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter [Theory] [WithBlankImages(1, 1, PixelTypes.All)] public void GetGlobalInstance(TestImageProvider _) - where T : struct, IPixel => Assert.NotNull(PixelOperations.Instance); + where T : unmanaged, IPixel => Assert.NotNull(PixelOperations.Instance); } +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter public abstract class PixelOperationsTests : MeasureFixture - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public const string SkipProfilingBenchmarks = #if true @@ -113,8 +115,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan())); } [Theory] @@ -138,18 +139,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { SRgbCompanding.Compress(ref v); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -158,62 +159,60 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); } [Theory] [MemberData(nameof(ArraySizesData))] public void FromPremultipliedVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, expected, - (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) - ); + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); } [Theory] [MemberData(nameof(ArraySizesData))] public void FromPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -222,36 +221,35 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] [MemberData(nameof(ArraySizesData))] public void FromCompandedPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); if (this.HasAlpha) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } SRgbCompanding.Compress(ref v); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -260,8 +258,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] @@ -274,26 +271,24 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan())); } - - public static readonly TheoryData Generic_To_Data = new TheoryData - { - default(Rgba32), - default(Bgra32), - default(Rgb24), - default(Gray8), - default(Gray16), - default(Rgb48), - default(Rgba64) - }; + public static readonly TheoryData Generic_To_Data = new TheoryData + { + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel() + }; [Theory] [MemberData(nameof(Generic_To_Data))] - public void Generic_To(TDestPixel dummy) - where TDestPixel : struct, IPixel + public void Generic_To(TestPixel dummy) + where TDestPixel : unmanaged, IPixel { const int Count = 2134; TPixel[] source = CreatePixelTestData(Count); @@ -304,7 +299,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation(source, expected, (s, d) => Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); } - [Theory] [MemberData(nameof(ArraySizesData))] public void ToScaledVector4(int count) @@ -326,18 +320,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void ToCompandedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { SRgbCompanding.Compress(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); } - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -346,50 +340,48 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); } [Theory] [MemberData(nameof(ArraySizesData))] public void ToPremultipliedVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } - TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, expected, - (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) - ); + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); } [Theory] [MemberData(nameof(ArraySizesData))] public void ToPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -405,20 +397,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void ToCompandedPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); SRgbCompanding.Compress(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -427,8 +419,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] @@ -448,8 +439,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -457,7 +447,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToArgb32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 4]; + byte[] expected = new byte[count * 4]; var argb = default(Argb32); for (int i = 0; i < count; i++) @@ -474,8 +464,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -495,8 +484,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -504,7 +492,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToBgr24Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 3]; + byte[] expected = new byte[count * 3]; var bgr = default(Bgr24); for (int i = 0; i < count; i++) @@ -519,8 +507,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -540,8 +527,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -549,7 +535,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToBgra32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 4]; + byte[] expected = new byte[count * 4]; var bgra = default(Bgra32); for (int i = 0; i < count; i++) @@ -565,8 +551,226 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgra5551Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + + Bgra5551 bgra = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromBgra5551(bgra); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToBgra5551Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + Bgra5551 bgra = default; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + bgra.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref bgra); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromL8(int count) + { + byte[] sourceBytes = CreateByteTestData(count); + L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromL8(source[i]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromL8(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToL8(int count) + { + TPixel[] source = CreatePixelTestData(count); + var expected = new L8[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToL8(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromL16(int count) + { + L16[] source = CreateVector4TestData(count).Select(v => + { + L16 g = default; + g.FromVector4(v); + return g; + }).ToArray(); + + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromL16(source[i]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromL16(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToL16(int count) + { + TPixel[] source = CreatePixelTestData(count); + var expected = new L16[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToL16(this.Configuration, s, d.GetSpan())); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromLa16Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + + La16 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromLa16(la); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToLa16Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + La16 la = default; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + la.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref la); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromLa32Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + + La32 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromLa32(la); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToLa32Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + La32 la = default; + + for (int i = 0; i < count; i++) + { + int offset = i * size; + la.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref la); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; + expected[offset + 2] = bytes[2]; + expected[offset + 3] = bytes[3]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -586,8 +790,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -595,7 +798,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgb24Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 3]; + byte[] expected = new byte[count * 3]; var rgb = default(Rgb24); for (int i = 0; i < count; i++) @@ -610,8 +813,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -631,8 +833,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -640,7 +841,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgba32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 4]; + byte[] expected = new byte[count * 4]; var rgba = default(Rgba32); for (int i = 0; i < count; i++) @@ -656,8 +857,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -677,8 +877,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -686,7 +885,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgb48Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 6]; + byte[] expected = new byte[count * 6]; Rgb48 rgb = default; for (int i = 0; i < count; i++) @@ -705,8 +904,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -726,8 +924,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -735,7 +932,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgba64Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 8]; + byte[] expected = new byte[count * 8]; Rgba64 rgba = default; for (int i = 0; i < count; i++) @@ -756,95 +953,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromGray8(int count) - { - byte[] sourceBytes = CreateByteTestData(count); - Gray8[] source = sourceBytes.Select(b => new Gray8(b)).ToArray(); - var expected = new TPixel[count]; - - - for (int i = 0; i < count; i++) - { - expected[i].FromGray8(source[i]); - } - - TestOperation( - source, - expected, - (s, d) => Operations.FromGray8(this.Configuration, s, d.GetSpan()) - ); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToGray8(int count) - { - TPixel[] source = CreatePixelTestData(count); - var expected = new Gray8[count]; - - for (int i = 0; i < count; i++) - { - expected[i].FromScaledVector4(source[i].ToScaledVector4()); - } - - TestOperation( - source, - expected, - (s, d) => Operations.ToGray8(this.Configuration, s, d.GetSpan()) - ); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromGray16(int count) - { - Gray16[] source = CreateVector4TestData(count).Select(v => - { - Gray16 g = default; - g.FromVector4(v); - return g; - }).ToArray(); - - var expected = new TPixel[count]; - - for (int i = 0; i < count; i++) - { - expected[i].FromGray16(source[i]); - } - - TestOperation( - source, - expected, - (s, d) => Operations.FromGray16(this.Configuration, s, d.GetSpan()) - ); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToGray16(int count) - { - TPixel[] source = CreatePixelTestData(count); - var expected = new Gray16[count]; - - for (int i = 0; i < count; i++) - { - expected[i].FromScaledVector4(source[i].ToScaledVector4()); - } - - TestOperation( - source, - expected, - (s, d) => Operations.ToGray16(this.Configuration, s, d.GetSpan()) - ); - } - public delegate void RefAction(ref T1 arg1); internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) @@ -905,6 +1016,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations result[i] = v; } + return result; } @@ -946,24 +1058,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations internal static byte[] CreateByteTestData(int length) { - var result = new byte[length]; + byte[] result = new byte[length]; var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { result[i] = (byte)rnd.Next(255); } + return result; } internal static Vector4 GetVector(Random rnd) { - return new Vector4( - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble() - ); + return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); } [StructLayout(LayoutKind.Sequential)] @@ -987,7 +1095,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations where TDest : struct { public TSource[] SourceBuffer { get; } + public IMemoryOwner ActualDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } public TestBuffers(TSource[] source, TDest[] expectedDest) @@ -1013,19 +1123,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { // ReSharper disable PossibleNullReferenceException Assert.Equal(expected[i], actual[i], comparer); - // ReSharper restore PossibleNullReferenceException - } - } - else if (typeof(TDest) == typeof(Gray16)) - { - // Minor difference is tolerated for 16 bit pixel values - Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); - Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); - for (int i = 0; i < count; i++) - { - int difference = expected[i].PackedValue - actual[i].PackedValue; - Assert.True(Math.Abs(difference) < 2); + // ReSharper restore PossibleNullReferenceException } } else diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs deleted file mode 100644 index 74360e8574..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.PixelFormats -{ - public class PixelBlenderTests - { - public static TheoryData BlenderMappings = new TheoryData - { - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - }; - - [Theory] - [MemberData(nameof(BlenderMappings))] - public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) - where TPixel : struct, IPixel - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); - Assert.IsType(type, blender); - } - - public static TheoryData ColorBlendingExpectedResults = new TheoryData - { - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Normal, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, - - }; - - [Theory] - [MemberData(nameof(ColorBlendingExpectedResults))] - public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult) - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); - - Rgba32 actualResult = blender.Blend(backdrop, source, opacity); - - // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); - } - - public static TheoryData AlphaCompositionExpectedResults = new TheoryData - { - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue }, - }; - - [Theory] - [MemberData(nameof(AlphaCompositionExpectedResults))] - public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult) - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode); - - Rgba32 actualResult = blender.Blend(backdrop, source, opacity); - - // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs index bccaaf8168..ad45b0771f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats float x = 0xb6dc; float y = 0xA59f; Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); - Assert.Equal((uint)6554, new Rg32(0.1f, -0.3f).PackedValue); + Assert.Equal(6554U, new Rg32(0.1f, -0.3f).PackedValue); // Test the limits. - Assert.Equal((uint)0x0, new Rg32(Vector2.Zero).PackedValue); + Assert.Equal(0x0U, new Rg32(Vector2.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs index 3bddc21ab3..6ab7b9c954 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var short3 = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); var expected = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - // act + // act Vector4 scaled = short3.ToScaledVector4(); pixel.FromScaledVector4(scaled); @@ -75,4 +75,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, rgb.B); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs index 3a28c82b7b..7b3f71985c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats float y = 0x36d; float z = 0x3b7; float w = 0x1; - Assert.Equal((uint)0x7B7DB6DB, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); + Assert.Equal(0x7B7DB6DBU, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); - Assert.Equal((uint)536871014, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal(536871014U, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); // Test the limits. - Assert.Equal((uint)0x0, new Rgba1010102(Vector4.Zero).PackedValue); + Assert.Equal(0x0U, new Rgba1010102(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); } @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats uint expectedPackedValue = uint.MaxValue; // act - rgba.FromGray8(new Gray8(byte.MaxValue)); + rgba.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expectedPackedValue, rgba.PackedValue); @@ -176,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats uint expectedPackedValue = uint.MaxValue; // act - rgba.FromGray16(new Gray16(ushort.MaxValue)); + rgba.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expectedPackedValue, rgba.PackedValue); diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index 1ab5f8e3df..6656ba19c2 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Rgba32(0, 0, 0); var color2 = new Rgba32(0, 0, 0, 1F); - var color3 = Rgba32.FromHex("#000"); - var color4 = Rgba32.FromHex("#000F"); - var color5 = Rgba32.FromHex("#000000"); - var color6 = Rgba32.FromHex("#000000FF"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000F"); + var color5 = Rgba32.ParseHex("#000000"); + var color6 = Rgba32.ParseHex("#000000FF"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); @@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Rgba32(255, 0, 0, 255); var color2 = new Rgba32(0, 0, 0, 255); - var color3 = Rgba32.FromHex("#000"); - var color4 = Rgba32.FromHex("#000000"); - var color5 = Rgba32.FromHex("#FF000000"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000000"); + var color5 = Rgba32.ParseHex("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void FromAndToHex() { // 8 digit hex matches css4 spec. RRGGBBAA - var color = Rgba32.FromHex("#AABBCCDD"); // 170, 187, 204, 221 + var color = Rgba32.ParseHex("#AABBCCDD"); // 170, 187, 204, 221 Assert.Equal(170, color.R); Assert.Equal(187, color.G); Assert.Equal(204, color.B); @@ -126,8 +126,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Rgba32_PackedValues() { Assert.Equal(0x80001Au, new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); + // Test the limits. - Assert.Equal((uint)0x0, new Rgba32(Vector4.Zero).PackedValue); + Assert.Equal(0x0U, new Rgba32(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); } @@ -204,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromRgba32(expected); actual.FromRgba32(rgba); @@ -220,7 +221,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra32); var expected = new Bgra32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromBgra32(expected); actual.FromRgba32(rgba); @@ -236,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Argb32); var expected = new Argb32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromArgb32(expected); actual.FromRgba32(rgba); diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index 9cdf4125c7..34ec0bdef0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -12,15 +12,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Rgba64_PackedValues() { - Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(5243, 9830, 19660, 29491).PackedValue); + Assert.Equal(0x73334CCC2666147BUL, new Rgba64(5243, 9830, 19660, 29491).PackedValue); // Test the limits. - Assert.Equal((ulong)0x0, new Rgba64(0, 0, 0, 0).PackedValue); - Assert.Equal(0xFFFFFFFFFFFFFFFF, new Rgba64( - ushort.MaxValue, - ushort.MaxValue, - ushort.MaxValue, - ushort.MaxValue).PackedValue); + Assert.Equal(0x0UL, new Rgba64(0, 0, 0, 0).PackedValue); + Assert.Equal( + 0xFFFFFFFFFFFFFFFF, + new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).PackedValue); // Test data ordering Assert.Equal(0xC7AD8F5C570A1EB8, new Rgba64(0x1EB8, 0x570A, 0x8F5C, 0xC7AD).PackedValue); @@ -36,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Theory] [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue/2, 100, 2222, 33333)] + [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] public void Rgba64_ToScaledVector4(ushort r, ushort g, ushort b, ushort a) { // arrange @@ -61,17 +59,15 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Theory] [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue/2, 100, 2222, 33333)] + [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] public void Rgba64_FromScaledVector4(ushort r, ushort g, ushort b, ushort a) { // arrange - var source = new Rgba64(r, g, b, a); // act Vector4 scaled = source.ToScaledVector4(); - Rgba64 actual = default; actual.FromScaledVector4(scaled); @@ -105,7 +101,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } - [Fact] public void Rgba64_FromBgra5551() { @@ -219,7 +214,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } - [Fact] public void ToRgba32_Retval() { diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs index fe2f8394ff..3a2841bb3b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 expected = Vector4.One; // act - rgba.FromGray16(new Gray16(ushort.MaxValue)); + rgba.FromL16(new L16(ushort.MaxValue)); // assert Assert.Equal(expected, rgba.ToScaledVector4()); @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 expected = Vector4.One; // act - rgba.FromGray8(new Gray8(byte.MaxValue)); + rgba.FromL8(new L8(byte.MaxValue)); // assert Assert.Equal(expected, rgba.ToScaledVector4()); diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index 4ae172ed4e..45f65eb4b7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -13,11 +13,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Short2_PackedValues() { // Test ordering - Assert.Equal((uint)0x361d2db1, new Short2(0x2db1, 0x361d).PackedValue); + Assert.Equal(0x361d2db1U, new Short2(0x2db1, 0x361d).PackedValue); Assert.Equal(4294639744, new Short2(127.5f, -5.3f).PackedValue); + // Test the limits. - Assert.Equal((uint)0x0, new Short2(Vector2.Zero).PackedValue); - Assert.Equal((uint)0x7FFF7FFF, new Short2(Vector2.One * 0x7FFF).PackedValue); + Assert.Equal(0x0U, new Short2(Vector2.Zero).PackedValue); + Assert.Equal(0x7FFF7FFFU, new Short2(Vector2.One * 0x7FFF).PackedValue); Assert.Equal(0x80008000, new Short2(Vector2.One * -0x8000).PackedValue); } @@ -34,9 +35,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Short2_ToVector4() { - Assert.Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), (new Short2(Vector2.One * 0x7FFF)).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), (new Short2(Vector2.Zero)).ToVector4()); - Assert.Equal(new Vector4(-0x8000, -0x8000, 0, 1), (new Short2(Vector2.One * -0x8000)).ToVector4()); + Assert.Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), new Short2(Vector2.One * 0x7FFF).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), new Short2(Vector2.Zero).ToVector4()); + Assert.Equal(new Vector4(-0x8000, -0x8000, 0, 1), new Short2(Vector2.One * -0x8000).ToVector4()); } [Fact] @@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var short2 = new Short2(Vector2.One * 0x7FFF); const ulong expected = 0x7FFF7FFF; - // act + // act Vector4 scaled = short2.ToScaledVector4(); pixel.FromScaledVector4(scaled); uint actual = pixel.PackedValue; @@ -102,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(20, 38, 0, 255); - // act + // act short2.FromRgba32(expected); short2.ToRgba32(ref actual); @@ -147,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { // arrange var short2 = default(Short2); - + // act short2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 45f8e25f43..54abf0db08 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var shortValue1 = new Short4(11547, 12653, 29623, 193); var shortValue2 = new Short4(0.1f, -0.3f, 0.5f, -0.7f); - Assert.Equal((ulong)0x00c173b7316d2d1b, shortValue1.PackedValue); + Assert.Equal(0x00c173b7316d2d1bUL, shortValue1.PackedValue); Assert.Equal(18446462598732840960, shortValue2.PackedValue); - Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x0UL, new Short4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); } @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(20, 38, 0, 255); - // act + // act short4.FromRgba32(expected); short4.ToRgba32(ref actual); @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra32); var expected = new Bgra32(20, 38, 0, 255); - // act + // act short4.FromBgra32(expected); Rgba32 temp = default; short4.ToRgba32(ref temp); @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Argb32); var expected = new Argb32(20, 38, 0, 255); - // act + // act short4.FromArgb32(expected); Rgba32 temp = default; short4.ToRgba32(ref temp); diff --git a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs index bd8c647421..162775a25f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Colors [Fact] public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() { - var color = Rgba32.FromHex("183060C0"); + var color = Rgba32.ParseHex("183060C0"); var colorVector = RgbaVector.FromHex("183060C0"); Assert.Equal(color.R, (byte)(colorVector.R * 255)); diff --git a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs index 2fbe260ecd..6b9b14bbfe 100644 --- a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs @@ -1,6 +1,8 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Globalization; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using Xunit; @@ -8,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives { public class ColorMatrixTests { - private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); [Fact] public void ColorMatrixIdentityIsCorrect() @@ -16,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives ColorMatrix val = default; val.M11 = val.M22 = val.M33 = val.M44 = 1F; - Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer); + Assert.Equal(val, ColorMatrix.Identity, this.approximateFloatComparer); } [Fact] @@ -48,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives ColorMatrix value1 = this.CreateAllTwos(); ColorMatrix value2 = this.CreateAllThrees(); - ColorMatrix m; + var m = default(ColorMatrix); // First row m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); @@ -80,14 +82,14 @@ namespace SixLabors.ImageSharp.Tests.Primitives m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; - Assert.Equal(m, value1 * value2, this.ApproximateFloatComparer); + Assert.Equal(m, value1 * value2, this.approximateFloatComparer); } [Fact] public void ColorMatrixMultiplyScalar() { ColorMatrix m = this.CreateAllTwos(); - Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer); + Assert.Equal(this.CreateAllFours(), m * 2, this.approximateFloatComparer); } [Fact] @@ -149,12 +151,14 @@ namespace SixLabors.ImageSharp.Tests.Primitives CultureInfo ci = CultureInfo.CurrentCulture; +#pragma warning disable SA1117 // Parameters should be on same line or separate lines string expected = string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", - m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), - m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), - m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), - m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), - m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); + m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), + m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), + m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), + m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), + m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); +#pragma warning restore SA1117 // Parameters should be on same line or separate lines Assert.Equal(expected, m.ToString()); } diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index d684198fa7..d515b21a9d 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Primitives; -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Primitives @@ -118,5 +116,25 @@ namespace SixLabors.ImageSharp.Tests.Primitives Assert.Equal(2, transposed[1, 0]); Assert.Equal(3, transposed[2, 0]); } + + [Fact] + public void DenseMatrixEquality() + { + var dense = new DenseMatrix(3, 1); + var dense2 = new DenseMatrix(3, 1); + var dense3 = new DenseMatrix(1, 3); + + Assert.True(dense == dense2); + Assert.False(dense != dense2); + Assert.Equal(dense, dense2); + Assert.Equal(dense, (object)dense2); + Assert.Equal(dense.GetHashCode(), dense2.GetHashCode()); + + Assert.False(dense == dense3); + Assert.True(dense != dense3); + Assert.NotEqual(dense, dense3); + Assert.NotEqual(dense, (object)dense3); + Assert.NotEqual(dense.GetHashCode(), dense3.GetHashCode()); + } } } diff --git a/tests/ImageSharp.Tests/Primitives/PointFTests.cs b/tests/ImageSharp.Tests/Primitives/PointFTests.cs new file mode 100644 index 0000000000..2bb4cc6dda --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/PointFTests.cs @@ -0,0 +1,213 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Numerics; +using System.Runtime.CompilerServices; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class PointFTests + { + private static readonly ApproximateFloatComparer ApproximateFloatComparer = + new ApproximateFloatComparer(1e-6f); + + [Fact] + public void CanReinterpretCastFromVector2() + { + var vector = new Vector2(1, 2); + + PointF point = Unsafe.As(ref vector); + + Assert.Equal(vector.X, point.X); + Assert.Equal(vector.Y, point.Y); + } + + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, PointF.Empty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(0.0, 0.0)] + public void NonDefaultConstructorTest(float x, float y) + { + var p1 = new PointF(x, y); + + Assert.Equal(x, p1.X); + Assert.Equal(y, p1.Y); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(PointF.Empty.IsEmpty); + Assert.True(default(PointF).IsEmpty); + Assert.True(new PointF(0, 0).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + public void IsEmptyRandomTest(float x, float y) + { + Assert.False(new PointF(x, y).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(float x, float y) + { + var p = new PointF(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); + + p.X = 10; + Assert.Equal(10, p.X); + + p.Y = -10.123f; + Assert.Equal(-10.123, p.Y, 3); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)] + [InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void ArithmeticTestWithSize(float x, float y, int x1, int y1) + { + var p = new PointF(x, y); + var s = new Size(x1, y1); + + var addExpected = new PointF(x + x1, y + y1); + var subExpected = new PointF(x - x1, y - y1); + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, PointF.Add(p, s)); + Assert.Equal(subExpected, PointF.Subtract(p, s)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTestWithSizeF(float x, float y) + { + var p = new PointF(x, y); + var s = new SizeF(y, x); + + var addExpected = new PointF(x + y, y + x); + var subExpected = new PointF(x - y, y - x); + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, PointF.Add(p, s)); + Assert.Equal(subExpected, PointF.Subtract(p, s)); + } + + [Fact] + public void RotateTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); + + var pout = PointF.Transform(p, matrix); + + Assert.Equal(-2.82842732F, pout.X, ApproximateFloatComparer); + Assert.Equal(21.2132034F, pout.Y, ApproximateFloatComparer); + } + + [Fact] + public void SkewTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, PointF.Empty); + + var pout = PointF.Transform(p, matrix); + Assert.Equal(new PointF(30, 30), pout); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(float x, float y) + { + var pLeft = new PointF(x, y); + var pRight = new PointF(y, x); + + if (x == y) + { + Assert.True(pLeft == pRight); + Assert.False(pLeft != pRight); + Assert.True(pLeft.Equals(pRight)); + Assert.True(pLeft.Equals((object)pRight)); + Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode()); + return; + } + + Assert.True(pLeft != pRight); + Assert.False(pLeft == pRight); + Assert.False(pLeft.Equals(pRight)); + Assert.False(pLeft.Equals((object)pRight)); + } + + [Fact] + public void EqualityTest_NotPointF() + { + var point = new PointF(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); + + // If PointF implements IEquatable (e.g. in .NET Core), then structs that are implicitly + // convertible to var can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToPointF = typeof(IEquatable).IsAssignableFrom(point.GetType()); + Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0))); + + Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast + } + + [Fact] + public void GetHashCodeTest() + { + var point = new PointF(10, 10); + Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode()); + } + + [Fact] + public void ToStringTest() + { + var p = new PointF(5.1F, -5.123F); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(float x, float y) + { + PointF p = new PointF(x, y); + + (float deconstructedX, float deconstructedY) = p; + + Assert.Equal(x, deconstructedX); + Assert.Equal(y, deconstructedY); + } + } +} diff --git a/tests/ImageSharp.Tests/Primitives/PointTests.cs b/tests/ImageSharp.Tests/Primitives/PointTests.cs new file mode 100644 index 0000000000..8e86c72188 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/PointTests.cs @@ -0,0 +1,258 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Numerics; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class PointTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, Point.Empty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(new Size(x, y)); + + Assert.Equal(p1, p2); + } + + [Theory] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(0)] + public void SingleIntConstructorTest(int x) + { + var p1 = new Point(x); + var p2 = new Point(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); + + Assert.Equal(p1, p2); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(Point.Empty.IsEmpty); + Assert.True(default(Point).IsEmpty); + Assert.True(new Point(0, 0).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int x, int y) + { + Assert.False(new Point(x, y).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(int x, int y) + { + var p = new Point(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void PointFConversionTest(int x, int y) + { + PointF p = new Point(x, y); + Assert.Equal(new PointF(x, y), p); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int x, int y) + { + var sz = (Size)new Point(x, y); + Assert.Equal(new Size(x, y), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int x, int y) + { + Point addExpected, subExpected, p = new Point(x, y); + var s = new Size(y, x); + + unchecked + { + addExpected = new Point(x + y, y + x); + subExpected = new Point(x - y, y - x); + } + + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, Point.Add(p, s)); + Assert.Equal(subExpected, Point.Subtract(p, s)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void PointFMathematicalTest(float x, float y) + { + var pf = new PointF(x, y); + Point pCeiling, pTruncate, pRound; + + unchecked + { + pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y)); + pTruncate = new Point((int)x, (int)y); + pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y)); + } + + Assert.Equal(pCeiling, Point.Ceiling(pf)); + Assert.Equal(pRound, Point.Round(pf)); + Assert.Equal(pTruncate, (Point)pf); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void OffsetTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(y, x); + + p1.Offset(p2); + + Assert.Equal(unchecked(p2.X + p2.Y), p1.X); + Assert.Equal(p1.X, p1.Y); + + p2.Offset(x, y); + Assert.Equal(p1, p2); + } + + [Fact] + public void RotateTest() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); + + var pout = Point.Transform(p, matrix); + + Assert.Equal(new Point(-3, 21), pout); + } + + [Fact] + public void SkewTest() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); + + var pout = Point.Transform(p, matrix); + Assert.Equal(new Point(30, 30), pout); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point((x / 2) - 1, (y / 2) - 1); + var p3 = new Point(x, y); + + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); + + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); + + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); + + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); + } + + [Fact] + public void EqualityTest_NotPoint() + { + var point = new Point(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); + Assert.False(point.Equals(new PointF(0, 0))); + } + + [Fact] + public void GetHashCodeTest() + { + var point = new Point(10, 10); + Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode()); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(1, -2, 3, -4)] + public void ConversionTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + RectangleF rectF = rect; + Assert.Equal(x, rectF.X); + Assert.Equal(y, rectF.Y); + Assert.Equal(width, rectF.Width); + Assert.Equal(height, rectF.Height); + } + + [Fact] + public void ToStringTest() + { + var p = new Point(5, -5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(int x, int y) + { + Point p = new Point(x, y); + + (int deconstructedX, int deconstructedY) = p; + + Assert.Equal(x, deconstructedX); + Assert.Equal(y, deconstructedY); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs new file mode 100644 index 0000000000..f0ba757160 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the struct. + /// + public class RectangleFTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, RectangleF.Empty); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void NonDefaultConstructorTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + var rect2 = new RectangleF(p, s); + + Assert.Equal(rect1, rect2); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void FromLTRBTest(float left, float top, float right, float bottom) + { + var expected = new RectangleF(left, top, right - left, bottom - top); + var actual = RectangleF.FromLTRB(left, top, right, bottom); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void DimensionsTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(x + width, rect.Right); + Assert.Equal(y + height, rect.Bottom); + } + + [Fact] + public void IsEmptyTest() + { + Assert.True(RectangleF.Empty.IsEmpty); + Assert.True(default(RectangleF).IsEmpty); + Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty); + Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty); + Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty); + + Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(float.MaxValue, float.MinValue)] + public void LocationSetTest(float x, float y) + { + var point = new PointF(x, y); + var rect = new RectangleF(10, 10, 10, 10) { Location = point }; + Assert.Equal(point, rect.Location); + Assert.Equal(point.X, rect.X); + Assert.Equal(point.Y, rect.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(float.MaxValue, float.MinValue)] + public void SizeSetTest(float x, float y) + { + var size = new SizeF(x, y); + var rect = new RectangleF(10, 10, 10, 10) { Size = size }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void EqualityTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(width, height, x, y); + + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); + } + + [Fact] + public void EqualityTestNotRectangleF() + { + var rectangle = new RectangleF(0, 0, 0, 0); + Assert.False(rectangle.Equals(null)); + Assert.False(rectangle.Equals(0)); + + // If RectangleF implements IEquatable (e.g. in .NET Core), then classes that are implicitly + // convertible to RectangleF can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToRectangleF = typeof(IEquatable).IsAssignableFrom(rectangle.GetType()); + Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0))); + + Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast + } + + [Fact] + public void GetHashCodeTest() + { + var rect1 = new RectangleF(10, 10, 10, 10); + var rect2 = new RectangleF(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void ContainsTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + float x1 = (x + width) / 2; + float y1 = (y + height) / 2; + var p = new PointF(x1, y1); + var r = new RectangleF(x1, y1, width / 2, height / 2); + + Assert.False(rect.Contains(x1, y1)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void InflateTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + var inflatedRect = new RectangleF(x - width, y - height, width + (2 * width), height + (2 * height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new SizeF(x, y); + inflatedRect = RectangleF.Inflate(rect, x, y); + + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void IntersectTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(y, x, width, height); + var expectedRect = RectangleF.Intersect(rect1, rect2); + rect1.Intersect(rect2); + Assert.Equal(expectedRect, rect1); + Assert.False(rect1.IntersectsWith(expectedRect)); + } + + [Fact] + public void IntersectIntersectingRectsTest() + { + var rect1 = new RectangleF(0, 0, 5, 5); + var rect2 = new RectangleF(1, 1, 3, 3); + var expected = new RectangleF(1, 1, 3, 3); + + Assert.Equal(expected, RectangleF.Intersect(rect1, rect2)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void UnionTest(float x, float y, float width, float height) + { + var a = new RectangleF(x, y, width, height); + var b = new RectangleF(width, height, x, y); + + float x1 = Math.Min(a.X, b.X); + float x2 = Math.Max(a.X + a.Width, b.X + b.Width); + float y1 = Math.Min(a.Y, b.Y); + float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + var expectedRectangle = new RectangleF(x1, y1, x2 - x1, y2 - y1); + + Assert.Equal(expectedRectangle, RectangleF.Union(a, b)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void OffsetTest(float x, float y, float width, float height) + { + var r1 = new RectangleF(x, y, width, height); + var expectedRect = new RectangleF(x + width, y + height, width, height); + var p = new PointF(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new RectangleF(5, 5.1F, 1.3F, 1); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); + } + + [Theory] + [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MinValue)] + [InlineData(0, 0, 0, 0)] + public void DeconstructTest(float x, float y, float width, float height) + { + RectangleF r = new RectangleF(x, y, width, height); + + (float dx, float dy, float dw, float dh) = r; + + Assert.Equal(x, dx); + Assert.Equal(y, dy); + Assert.Equal(width, dw); + Assert.Equal(height, dh); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs new file mode 100644 index 0000000000..acfbe9e61c --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs @@ -0,0 +1,336 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the struct. + /// + public class RectangleTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, Rectangle.Empty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonDefaultConstructorTest(int x, int y, int width, int height) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(new Point(x, y), new Size(width, height)); + + Assert.Equal(rect1, rect2); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void FromLTRBTest(int left, int top, int right, int bottom) + { + var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); + var rect2 = Rectangle.FromLTRB(left, top, right, bottom); + + Assert.Equal(rect1, rect2); + } + + [Fact] + public void EmptyTest() + { + Assert.True(Rectangle.Empty.IsEmpty); + Assert.True(default(Rectangle).IsEmpty); + Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonEmptyTest(int x, int y, int width, int height) + { + Assert.False(new Rectangle(x, y, width, height).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void DimensionsTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + Assert.Equal(new Point(x, y), rect.Location); + Assert.Equal(new Size(width, height), rect.Size); + + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + + var p = new Point(width, height); + var s = new Size(x, y); + rect.Location = p; + rect.Size = s; + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + + Assert.Equal(width, rect.X); + Assert.Equal(height, rect.Y); + Assert.Equal(x, rect.Width); + Assert.Equal(y, rect.Height); + Assert.Equal(width, rect.Left); + Assert.Equal(height, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public void LocationSetTest(int x, int y) + { + var point = new Point(x, y); + var rect = new Rectangle(10, 10, 10, 10) { Location = point }; + Assert.Equal(point, rect.Location); + Assert.Equal(point.X, rect.X); + Assert.Equal(point.Y, rect.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public void SizeSetTest(int x, int y) + { + var size = new Size(x, y); + var rect = new Rectangle(10, 10, 10, 10) { Size = size }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void EqualityTest(int x, int y, int width, int height) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(width / 2, height / 2, x, y); + + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); + } + + [Fact] + public void EqualityTestNotRectangle() + { + var rectangle = new Rectangle(0, 0, 0, 0); + Assert.False(rectangle.Equals(null)); + Assert.False(rectangle.Equals(0)); + Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0))); + } + + [Fact] + public void GetHashCodeTest() + { + var rect1 = new Rectangle(10, 10, 10, 10); + var rect2 = new Rectangle(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void RectangleFConversionTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + Rectangle rCeiling, rTruncate, rRound; + + unchecked + { + rCeiling = new Rectangle( + (int)Math.Ceiling(x), + (int)Math.Ceiling(y), + (int)Math.Ceiling(width), + (int)Math.Ceiling(height)); + + rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height); + + rRound = new Rectangle( + (int)Math.Round(x), + (int)Math.Round(y), + (int)Math.Round(width), + (int)Math.Round(height)); + } + + Assert.Equal(rCeiling, Rectangle.Ceiling(rect)); + Assert.Equal(rTruncate, Rectangle.Truncate(rect)); + Assert.Equal(rRound, Rectangle.Round(rect)); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void ContainsTest(int x, int y, int width, int height) + { + var rect = new Rectangle(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); + var p = new Point(x, y); + var r = new Rectangle(x, y, width / 2, height / 2); + + Assert.False(rect.Contains(x, y)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void InflateTest(int x, int y, int width, int height) + { + Rectangle inflatedRect, rect = new Rectangle(x, y, width, height); + unchecked + { + inflatedRect = new Rectangle(x - width, y - height, width + (2 * width), height + (2 * height)); + } + + Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new Size(x, y); + unchecked + { + inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + (2 * x), rect.Height + (2 * y)); + } + + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void IntersectTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + var expectedRect = Rectangle.Intersect(rect, rect); + rect.Intersect(rect); + Assert.Equal(expectedRect, rect); + Assert.False(rect.IntersectsWith(expectedRect)); + } + + [Fact] + public void IntersectIntersectingRectsTest() + { + var rect1 = new Rectangle(0, 0, 5, 5); + var rect2 = new Rectangle(1, 1, 3, 3); + var expected = new Rectangle(1, 1, 3, 3); + + Assert.Equal(expected, Rectangle.Intersect(rect1, rect2)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void UnionTest(int x, int y, int width, int height) + { + var a = new Rectangle(x, y, width, height); + var b = new Rectangle(width, height, x, y); + + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.X + a.Width, b.X + b.Width); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + var expectedRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); + + Assert.Equal(expectedRectangle, Rectangle.Union(a, b)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void OffsetTest(int x, int y, int width, int height) + { + var r1 = new Rectangle(x, y, width, height); + var expectedRect = new Rectangle(x + width, y + height, width, height); + var p = new Point(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new Rectangle(5, -5, 0, 1); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); + } + + [Theory] + [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MinValue)] + [InlineData(0, 0, 0, 0)] + public void DeconstructTest(int x, int y, int width, int height) + { + var r = new Rectangle(x, y, width, height); + + (int dx, int dy, int dw, int dh) = r; + + Assert.Equal(x, dx); + Assert.Equal(y, dy); + Assert.Equal(width, dw); + Assert.Equal(height, dh); + } + } +} diff --git a/tests/ImageSharp.Tests/Primitives/SizeFTests.cs b/tests/ImageSharp.Tests/Primitives/SizeFTests.cs new file mode 100644 index 0000000000..8cda5d4ebf --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/SizeFTests.cs @@ -0,0 +1,249 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class SizeFTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, SizeF.Empty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorAndDimensionsTest(float width, float height) + { + var s1 = new SizeF(width, height); + var p1 = new PointF(width, height); + var s2 = new SizeF(s1); + + Assert.Equal(s1, s2); + Assert.Equal(s1, new SizeF(p1)); + Assert.Equal(s2, new SizeF(p1)); + + Assert.Equal(width, s1.Width); + Assert.Equal(height, s1.Height); + + s1.Width = 10; + Assert.Equal(10, s1.Width); + + s1.Height = -10.123f; + Assert.Equal(-10.123, s1.Height, 3); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(SizeF.Empty.IsEmpty); + Assert.True(default(SizeF).IsEmpty); + Assert.True(new SizeF(0, 0).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + public void IsEmptyRandomTest(float width, float height) + { + Assert.False(new SizeF(width, height).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(float width, float height) + { + var s1 = new SizeF(width, height); + var s2 = new SizeF(height, width); + var addExpected = new SizeF(width + height, width + height); + var subExpected = new SizeF(width - height, height - width); + + Assert.Equal(addExpected, s1 + s2); + Assert.Equal(addExpected, SizeF.Add(s1, s2)); + + Assert.Equal(subExpected, s1 - s2); + Assert.Equal(subExpected, SizeF.Subtract(s1, s2)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(float width, float height) + { + var sLeft = new SizeF(width, height); + var sRight = new SizeF(height, width); + + if (width == height) + { + Assert.True(sLeft == sRight); + Assert.False(sLeft != sRight); + Assert.True(sLeft.Equals(sRight)); + Assert.True(sLeft.Equals((object)sRight)); + Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode()); + return; + } + + Assert.True(sLeft != sRight); + Assert.False(sLeft == sRight); + Assert.False(sLeft.Equals(sRight)); + Assert.False(sLeft.Equals((object)sRight)); + } + + [Fact] + public void EqualityTest_NotSizeF() + { + var size = new SizeF(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); + + // If SizeF implements IEquatable (e.g in .NET Core), then classes that are implicitly + // convertible to SizeF can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToSizeF = typeof(IEquatable).IsAssignableFrom(size.GetType()); + Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0))); + + Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast + } + + [Fact] + public void GetHashCodeTest() + { + var size = new SizeF(10, 10); + Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void ConversionTest(float width, float height) + { + var s1 = new SizeF(width, height); + var p1 = (PointF)s1; + var s2 = new Size(unchecked((int)width), unchecked((int)height)); + + Assert.Equal(new PointF(width, height), p1); + Assert.Equal(p1, (PointF)s1); + Assert.Equal(s2, (Size)s1); + } + + [Fact] + public void ToStringTest() + { + var sz = new SizeF(10, 5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); + } + + [Theory] + [InlineData(1000.234f, 0.0f)] + [InlineData(1000.234f, 1.0f)] + [InlineData(1000.234f, 2400.933f)] + [InlineData(1000.234f, float.MaxValue)] + [InlineData(1000.234f, -1.0f)] + [InlineData(1000.234f, -2400.933f)] + [InlineData(1000.234f, float.MinValue)] + [InlineData(float.MaxValue, 0.0f)] + [InlineData(float.MaxValue, 1.0f)] + [InlineData(float.MaxValue, 2400.933f)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, -1.0f)] + [InlineData(float.MaxValue, -2400.933f)] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, 0.0f)] + [InlineData(float.MinValue, 1.0f)] + [InlineData(float.MinValue, 2400.933f)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, -1.0f)] + [InlineData(float.MinValue, -2400.933f)] + [InlineData(float.MinValue, float.MinValue)] + public void MultiplicationTest(float dimension, float multiplier) + { + SizeF sz1 = new SizeF(dimension, dimension); + SizeF mulExpected; + + mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); + + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } + + [Theory] + [InlineData(1111.1111f, 2222.2222f, 3333.3333f)] + public void MultiplicationTestWidthHeightMultiplier(float width, float height, float multiplier) + { + SizeF sz1 = new SizeF(width, height); + SizeF mulExpected; + + mulExpected = new SizeF(width * multiplier, height * multiplier); + + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } + + [Theory] + [InlineData(0.0f, 1.0f)] + [InlineData(1.0f, 1.0f)] + [InlineData(-1.0f, 1.0f)] + [InlineData(1.0f, -1.0f)] + [InlineData(-1.0f, -1.0f)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, 1.0f)] + [InlineData(float.MinValue, 1.0f)] + [InlineData(float.MaxValue, -1.0f)] + [InlineData(float.MinValue, -1.0f)] + [InlineData(float.MinValue, 0.0f)] + [InlineData(1.0f, float.MinValue)] + [InlineData(1.0f, float.MaxValue)] + [InlineData(-1.0f, float.MinValue)] + [InlineData(-1.0f, float.MaxValue)] + public void DivideTestSizeFloat(float dimension, float divisor) + { + SizeF size = new SizeF(dimension, dimension); + SizeF expected = new SizeF(dimension / divisor, dimension / divisor); + Assert.Equal(expected, size / divisor); + } + + [Theory] + [InlineData(-111.111f, 222.222f, 333.333f)] + public void DivideTestSizeFloatWidthHeightDivisor(float width, float height, float divisor) + { + SizeF size = new SizeF(width, height); + SizeF expected = new SizeF(width / divisor, height / divisor); + Assert.Equal(expected, size / divisor); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(float width, float height) + { + SizeF s = new SizeF(width, height); + + (float deconstructedWidth, float deconstructedHeight) = s; + + Assert.Equal(width, deconstructedWidth); + Assert.Equal(height, deconstructedHeight); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/SizeTests.cs b/tests/ImageSharp.Tests/Primitives/SizeTests.cs new file mode 100644 index 0000000000..4aea060366 --- /dev/null +++ b/tests/ImageSharp.Tests/Primitives/SizeTests.cs @@ -0,0 +1,379 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the struct. + /// + public class SizeTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, Size.Empty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int width, int height) + { + var s1 = new Size(width, height); + var s2 = new Size(new Point(width, height)); + + Assert.Equal(s1, s2); + + s1.Width = 10; + Assert.Equal(10, s1.Width); + + s1.Height = -10; + Assert.Equal(-10, s1.Height); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(Size.Empty.IsEmpty); + Assert.True(default(Size).IsEmpty); + Assert.True(new Size(0, 0).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int width, int height) + { + Assert.False(new Size(width, height).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DimensionsTest(int width, int height) + { + var p = new Size(width, height); + Assert.Equal(width, p.Width); + Assert.Equal(height, p.Height); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void PointFConversionTest(int width, int height) + { + SizeF sz = new Size(width, height); + Assert.Equal(new SizeF(width, height), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int width, int height) + { + var sz = (Point)new Size(width, height); + Assert.Equal(new Point(width, height), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int width, int height) + { + var sz1 = new Size(width, height); + var sz2 = new Size(height, width); + Size addExpected, subExpected; + + unchecked + { + addExpected = new Size(width + height, height + width); + subExpected = new Size(width - height, height - width); + } + + Assert.Equal(addExpected, sz1 + sz2); + Assert.Equal(subExpected, sz1 - sz2); + Assert.Equal(addExpected, Size.Add(sz1, sz2)); + Assert.Equal(subExpected, Size.Subtract(sz1, sz2)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void PointFMathematicalTest(float width, float height) + { + var szF = new SizeF(width, height); + Size pCeiling, pTruncate, pRound; + + unchecked + { + pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height)); + pTruncate = new Size((int)width, (int)height); + pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height)); + } + + Assert.Equal(pCeiling, Size.Ceiling(szF)); + Assert.Equal(pRound, Size.Round(szF)); + Assert.Equal(pTruncate, (Size)szF); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int width, int height) + { + var p1 = new Size(width, height); + var p2 = new Size(unchecked(width - 1), unchecked(height - 1)); + var p3 = new Size(width, height); + + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); + + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); + + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); + + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); + } + + [Fact] + public void EqualityTest_NotSize() + { + var size = new Size(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); + Assert.False(size.Equals(new SizeF(0, 0))); + } + + [Fact] + public void GetHashCodeTest() + { + var size = new Size(10, 10); + Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode()); + } + + [Fact] + public void ToStringTest() + { + var sz = new Size(10, 5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); + } + + [Theory] + [InlineData(1000, 0)] + [InlineData(1000, 1)] + [InlineData(1000, 2400)] + [InlineData(1000, int.MaxValue)] + [InlineData(1000, -1)] + [InlineData(1000, -2400)] + [InlineData(1000, int.MinValue)] + [InlineData(int.MaxValue, 0)] + [InlineData(int.MaxValue, 1)] + [InlineData(int.MaxValue, 2400)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, -1)] + [InlineData(int.MaxValue, -2400)] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, 0)] + [InlineData(int.MinValue, 1)] + [InlineData(int.MinValue, 2400)] + [InlineData(int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, -1)] + [InlineData(int.MinValue, -2400)] + [InlineData(int.MinValue, int.MinValue)] + public void MultiplicationTestSizeInt(int dimension, int multiplier) + { + Size sz1 = new Size(dimension, dimension); + Size mulExpected; + + unchecked + { + mulExpected = new Size(dimension * multiplier, dimension * multiplier); + } + + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } + + [Theory] + [InlineData(1000, 2000, 3000)] + public void MultiplicationTestSizeIntWidthHeightMultiplier(int width, int height, int multiplier) + { + Size sz1 = new Size(width, height); + Size mulExpected; + + unchecked + { + mulExpected = new Size(width * multiplier, height * multiplier); + } + + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } + + [Theory] + [InlineData(1000, 0.0f)] + [InlineData(1000, 1.0f)] + [InlineData(1000, 2400.933f)] + [InlineData(1000, float.MaxValue)] + [InlineData(1000, -1.0f)] + [InlineData(1000, -2400.933f)] + [InlineData(1000, float.MinValue)] + [InlineData(int.MaxValue, 0.0f)] + [InlineData(int.MaxValue, 1.0f)] + [InlineData(int.MaxValue, 2400.933f)] + [InlineData(int.MaxValue, float.MaxValue)] + [InlineData(int.MaxValue, -1.0f)] + [InlineData(int.MaxValue, -2400.933f)] + [InlineData(int.MaxValue, float.MinValue)] + [InlineData(int.MinValue, 0.0f)] + [InlineData(int.MinValue, 1.0f)] + [InlineData(int.MinValue, 2400.933f)] + [InlineData(int.MinValue, float.MaxValue)] + [InlineData(int.MinValue, -1.0f)] + [InlineData(int.MinValue, -2400.933f)] + [InlineData(int.MinValue, float.MinValue)] + public void MultiplicationTestSizeFloat(int dimension, float multiplier) + { + Size sz1 = new Size(dimension, dimension); + SizeF mulExpected; + + mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); + + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } + + [Theory] + [InlineData(1000, 2000, 30.33f)] + public void MultiplicationTestSizeFloatWidthHeightMultiplier(int width, int height, float multiplier) + { + Size sz1 = new Size(width, height); + SizeF mulExpected; + + mulExpected = new SizeF(width * multiplier, height * multiplier); + + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } + + [Fact] + public void DivideByZeroChecks() + { + Size size = new Size(100, 100); + Assert.Throws(() => size / 0); + + SizeF expectedSizeF = new SizeF(float.PositiveInfinity, float.PositiveInfinity); + Assert.Equal(expectedSizeF, size / 0.0f); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(-1, 1)] + [InlineData(1, -1)] + [InlineData(-1, -1)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, 1)] + [InlineData(int.MinValue, 1)] + [InlineData(int.MaxValue, -1)] + public void DivideTestSizeInt(int dimension, int divisor) + { + Size size = new Size(dimension, dimension); + Size expected; + + expected = new Size(dimension / divisor, dimension / divisor); + + Assert.Equal(expected, size / divisor); + } + + [Theory] + [InlineData(1111, 2222, 3333)] + public void DivideTestSizeIntWidthHeightDivisor(int width, int height, int divisor) + { + Size size = new Size(width, height); + Size expected; + + expected = new Size(width / divisor, height / divisor); + + Assert.Equal(expected, size / divisor); + } + + [Theory] + [InlineData(0, 1.0f)] + [InlineData(1, 1.0f)] + [InlineData(-1, 1.0f)] + [InlineData(1, -1.0f)] + [InlineData(-1, -1.0f)] + [InlineData(int.MaxValue, float.MaxValue)] + [InlineData(int.MaxValue, float.MinValue)] + [InlineData(int.MinValue, float.MaxValue)] + [InlineData(int.MinValue, float.MinValue)] + [InlineData(int.MaxValue, 1.0f)] + [InlineData(int.MinValue, 1.0f)] + [InlineData(int.MaxValue, -1.0f)] + [InlineData(int.MinValue, -1.0f)] + public void DivideTestSizeFloat(int dimension, float divisor) + { + SizeF size = new SizeF(dimension, dimension); + SizeF expected; + + expected = new SizeF(dimension / divisor, dimension / divisor); + Assert.Equal(expected, size / divisor); + } + + [Theory] + [InlineData(1111, 2222, -333.33f)] + public void DivideTestSizeFloatWidthHeightDivisor(int width, int height, float divisor) + { + SizeF size = new SizeF(width, height); + SizeF expected; + + expected = new SizeF(width / divisor, height / divisor); + Assert.Equal(expected, size / divisor); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(int width, int height) + { + Size s = new Size(width, height); + + (int deconstructedWidth, int deconstructedHeight) = s; + + Assert.Equal(width, deconstructedWidth); + Assert.Equal(height, deconstructedHeight); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs index 140af9563a..953563006b 100644 --- a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.ComponentModel.DataAnnotations; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; using Xunit; @@ -21,10 +22,11 @@ namespace SixLabors.ImageSharp.Tests.Processing public BaseImageOperationsExtensionTest() { - this.options = new GraphicsOptions(false); + this.options = new GraphicsOptions { Antialias = false }; this.source = new Image(91 + 324, 123 + 56); this.rect = new Rectangle(91, 123, 324, 56); // make this random? - this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source, false); + this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source.GetConfiguration(), this.source, false); + this.internalOperations.SetGraphicsOptions(this.options); this.operations = this.internalOperations; } @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing { return Assert.IsType(operation.NonGenericProcessor); } - + return Assert.IsType(operation.GenericProcessor); } @@ -49,12 +51,12 @@ namespace SixLabors.ImageSharp.Tests.Processing FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; Assert.Equal(rect, operation.Rectangle); - + if (operation.NonGenericProcessor != null) { return Assert.IsType(operation.NonGenericProcessor); } - + return Assert.IsType(operation.GenericProcessor); } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs new file mode 100644 index 0000000000..f992ac35b3 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest + { + [Fact] + public void AdaptiveThreshold_UsesDefaults_Works() + { + // arrange + var expectedThresholdLimit = .85f; + Color expectedUpper = Color.White; + Color expectedLower = Color.Black; + + // act + this.operations.AdaptiveThreshold(); + + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingThresholdLimit_Works() + { + // arrange + var expectedThresholdLimit = .65f; + + // act + this.operations.AdaptiveThreshold(expectedThresholdLimit); + + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(Color.White, p.Upper); + Assert.Equal(Color.Black, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() + { + // arrange + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower); + + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() + { + // arrange + var expectedThresholdLimit = .77f; + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); + + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() + { + // arrange + var expectedThresholdLimit = .77f; + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit, this.rect); + + // assert + AdaptiveThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + public void AdaptiveThreshold_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(img => img.AdaptiveThreshold()); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + + [Theory] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + public void AdaptiveThreshold_WithRectangle_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, new Rectangle(60, 90, 200, 30))); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs deleted file mode 100644 index 531ad69b83..0000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Processing.Binarization -{ - public class BinaryDitherTest : BaseImageOperationsExtensionTest - { - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - - public BinaryDitherTest() - { - this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; - } - - [Fact] - public void BinaryDither_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither); - BinaryOrderedDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_rect_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, this.rect); - BinaryOrderedDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - [Fact] - public void BinaryDither_index_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink); - BinaryOrderedDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.Yellow, p.UpperColor); - Assert.Equal(Color.HotPink, p.LowerColor); - } - - [Fact] - public void BinaryDither_index_rect_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink, this.rect); - BinaryOrderedDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.HotPink, p.LowerColor); - } - - - [Fact] - public void BinaryDither_ErrorDiffuser_CorrectProcessor() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .4F); - BinaryErrorDiffusionProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.4F, p.Threshold); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.3F, p.Threshold); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.Yellow); - BinaryErrorDiffusionProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.Yellow, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs index c98f910464..5d550a595d 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Dithering; using Xunit; @@ -10,6 +9,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { public class OrderedDitherFactoryTests { +#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row + private static readonly DenseMatrix Expected2x2Matrix = new DenseMatrix( new uint[2, 2] { @@ -47,6 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { 63, 31, 55, 23, 61, 29, 53, 21 } }); +#pragma warning restore SA1025 // Code should not contain multiple whitespace in a row [Fact] public void OrderedDitherFactoryCreatesCorrect2x2Matrix() @@ -100,4 +102,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 6a864d83a4..e0b1e1bbb1 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,7 +13,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution { public class DetectEdgesTest : BaseImageOperationsExtensionTest { - [Fact] public void DetectEdges_SobelProcessorDefaultsSet() { @@ -33,17 +32,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution SobelProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); } - public static IEnumerable EdgeDetectionTheoryData => new[] { - new object[]{ new TestType(), EdgeDetectionOperators.Kayyali }, - new object[]{ new TestType(), EdgeDetectionOperators.Kirsch }, - new object[]{ new TestType(), EdgeDetectionOperators.Laplacian3x3 }, - new object[]{ new TestType(), EdgeDetectionOperators.Laplacian5x5 }, - new object[]{ new TestType(), EdgeDetectionOperators.LaplacianOfGaussian }, - new object[]{ new TestType(), EdgeDetectionOperators.Prewitt }, - new object[]{ new TestType(), EdgeDetectionOperators.RobertsCross }, - new object[]{ new TestType(), EdgeDetectionOperators.Robinson }, - new object[]{ new TestType(), EdgeDetectionOperators.Scharr }, - new object[]{ new TestType(), EdgeDetectionOperators.Sobel }, + + public static IEnumerable EdgeDetectionTheoryData => new[] + { + new object[] { new TestType(), EdgeDetectionOperators.Kayyali }, + new object[] { new TestType(), EdgeDetectionOperators.Kirsch }, + new object[] { new TestType(), EdgeDetectionOperators.Laplacian3x3 }, + new object[] { new TestType(), EdgeDetectionOperators.Laplacian5x5 }, + new object[] { new TestType(), EdgeDetectionOperators.LaplacianOfGaussian }, + new object[] { new TestType(), EdgeDetectionOperators.Prewitt }, + new object[] { new TestType(), EdgeDetectionOperators.RobertsCross }, + new object[] { new TestType(), EdgeDetectionOperators.Robinson }, + new object[] { new TestType(), EdgeDetectionOperators.Scharr }, + new object[] { new TestType(), EdgeDetectionOperators.Sobel }, }; [Theory] @@ -71,4 +72,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution Assert.Equal(grey, processor.Grayscale); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs index 8b3524fe66..ff20e3b9d2 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; @@ -23,11 +22,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution private static readonly DenseMatrix Expected5x5Matrix = new DenseMatrix( new float[,] { - { -1, -1, -1,-1, -1 }, - { -1, -1, -1,-1, -1 }, - { -1, -1, 24,-1, -1 }, - { -1, -1, -1,-1, -1 }, - { -1, -1, -1,-1, -1 } + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, 24, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }); [Fact] @@ -65,4 +64,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index c5d18cbb2b..0cc8db6518 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -2,11 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; - using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization @@ -20,10 +17,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization True(a.SequenceEqual(b)); } } - - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - private readonly Color[] TestPalette = + + private readonly IDither orderedDither; + private readonly IDither errorDiffuser; + private readonly Color[] testPalette = { Color.Red, Color.Green, @@ -32,15 +29,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public DitherTest() { - this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; + this.orderedDither = KnownDitherings.Bayer4x4; + this.errorDiffuser = KnownDitherings.FloydSteinberg; } [Fact] public void Dither_CorrectProcessor() { this.operations.Dither(this.orderedDither); - OrderedDitherPaletteProcessor p = this.Verify(); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } @@ -49,67 +46,129 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_rect_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.rect); - OrderedDitherPaletteProcessor p = this.Verify(this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } + [Fact] public void Dither_index_CorrectProcessor() { - this.operations.Dither(this.orderedDither, this.TestPalette); - OrderedDitherPaletteProcessor p = this.Verify(); + this.operations.Dither(this.orderedDither, this.testPalette); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.TestPalette, p.Palette); + Assert.Equal(this.testPalette, p.Palette); } [Fact] public void Dither_index_rect_CorrectProcessor() { - this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); - OrderedDitherPaletteProcessor p = this.Verify(this.rect); + this.operations.Dither(this.orderedDither, this.testPalette, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.TestPalette, p.Palette); + Assert.Equal(this.testPalette, p.Palette); } - [Fact] public void Dither_ErrorDiffuser_CorrectProcessor() { - this.operations.Diffuse(this.errorDiffuser, .4F); - ErrorDiffusionPaletteProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.4F, p.Threshold); + this.operations.Dither(this.errorDiffuser); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_rect_CorrectProcessor() { - this.operations.Diffuse(this.errorDiffuser, .3F, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.3F, p.Threshold); + this.operations.Dither(this.errorDiffuser, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_CorrectProcessorWithColors() { - this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette); - ErrorDiffusionPaletteProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(this.TestPalette, p.Palette); + this.operations.Dither(this.errorDiffuser, this.testPalette); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); + Assert.Equal(this.testPalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() { - this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(this.TestPalette, p.Palette); + this.operations.Dither(this.errorDiffuser, this.testPalette, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); + Assert.Equal(this.errorDiffuser, p.Dither); + Assert.Equal(this.testPalette, p.Palette); + } + + [Fact] + public void ErrorDitherEquality() + { + IDither dither = KnownDitherings.FloydSteinberg; + ErrorDither dither2 = ErrorDither.FloydSteinberg; + ErrorDither dither3 = ErrorDither.FloydSteinberg; + + Assert.True(dither == dither2); + Assert.True(dither2 == dither); + Assert.False(dither != dither2); + Assert.False(dither2 != dither); + Assert.Equal(dither, dither2); + Assert.Equal(dither, (object)dither2); + Assert.Equal(dither.GetHashCode(), dither2.GetHashCode()); + + dither = null; + Assert.False(dither == dither2); + Assert.False(dither2 == dither); + Assert.True(dither != dither2); + Assert.True(dither2 != dither); + Assert.NotEqual(dither, dither2); + Assert.NotEqual(dither, (object)dither2); + Assert.NotEqual(dither?.GetHashCode(), dither2.GetHashCode()); + + Assert.True(dither2 == dither3); + Assert.True(dither3 == dither2); + Assert.False(dither2 != dither3); + Assert.False(dither3 != dither2); + Assert.Equal(dither2, dither3); + Assert.Equal(dither2, (object)dither3); + Assert.Equal(dither2.GetHashCode(), dither3.GetHashCode()); + } + + [Fact] + public void OrderedDitherEquality() + { + IDither dither = KnownDitherings.Bayer2x2; + OrderedDither dither2 = OrderedDither.Bayer2x2; + OrderedDither dither3 = OrderedDither.Bayer2x2; + + Assert.True(dither == dither2); + Assert.True(dither2 == dither); + Assert.False(dither != dither2); + Assert.False(dither2 != dither); + Assert.Equal(dither, dither2); + Assert.Equal(dither, (object)dither2); + Assert.Equal(dither.GetHashCode(), dither2.GetHashCode()); + + dither = null; + Assert.False(dither == dither2); + Assert.False(dither2 == dither); + Assert.True(dither != dither2); + Assert.True(dither2 != dither); + Assert.NotEqual(dither, dither2); + Assert.NotEqual(dither, (object)dither2); + Assert.NotEqual(dither?.GetHashCode(), dither2.GetHashCode()); + + Assert.True(dither2 == dither3); + Assert.True(dither3 == dither2); + Assert.False(dither2 != dither3); + Assert.False(dither3 != dither2); + Assert.Equal(dither2, dither3); + Assert.Equal(dither2, (object)dither3); + Assert.Equal(dither2.GetHashCode(), dither3.GetHashCode()); } } } diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs index 1b5bd656dc..34b99461d3 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Effects @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() { this.operations.BackgroundColor(Color.BlanchedAlmond); - var processor = this.Verify(); + BackgroundColorProcessor processor = this.Verify(); - Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); + Assert.Equal(this.options, processor.GraphicsOptions); Assert.Equal(Color.BlanchedAlmond, processor.Color); } @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet() { this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); - var processor = this.Verify(this.rect); + BackgroundColorProcessor processor = this.Verify(this.rect); - Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); + Assert.Equal(this.options, processor.GraphicsOptions); Assert.Equal(Color.BlanchedAlmond, processor.Color); } @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet() { this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); - var processor = this.Verify(); + BackgroundColorProcessor processor = this.Verify(); Assert.Equal(this.options, processor.GraphicsOptions); Assert.Equal(Color.BlanchedAlmond, processor.Color); @@ -44,10 +44,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects public void BackgroundColor_amount_rect_options_BackgroundColorProcessorDefaultsSet() { this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); - var processor = this.Verify(this.rect); + BackgroundColorProcessor processor = this.Verify(this.rect); Assert.Equal(this.options, processor.GraphicsOptions); Assert.Equal(Color.BlanchedAlmond, processor.Color); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs index 18842e1f36..797423394e 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(10, processor.Levels); Assert.Equal(15, processor.BrushSize); } + [Fact] public void OilPaint_Levels_Brush_OilPaintingProcessorDefaultsSet() { @@ -48,4 +49,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(43, processor.BrushSize); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs index 38d53c7b6c..cd4d782792 100644 --- a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs @@ -3,52 +3,50 @@ using System.Collections.Generic; using System.Linq; - -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing { internal class FakeImageOperationsProvider : IImageProcessingContextFactory { - private List ImageOperators = new List(); + private readonly List imageOperators = new List(); public bool HasCreated(Image source) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return this.Created(source).Any(); } - public IEnumerable> Created(Image source) where TPixel : struct, IPixel + + public IEnumerable> Created(Image source) + where TPixel : unmanaged, IPixel { - return this.ImageOperators.OfType>() + return this.imageOperators.OfType>() .Where(x => x.Source == source); } - public IEnumerable.AppliedOperation> AppliedOperations(Image source) where TPixel : struct, IPixel + public IEnumerable.AppliedOperation> AppliedOperations(Image source) + where TPixel : unmanaged, IPixel { return this.Created(source) .SelectMany(x => x.Applied); } - public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) where TPixel : struct, IPixel + public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel { - var op = new FakeImageOperations(source, mutate); - this.ImageOperators.Add(op); + var op = new FakeImageOperations(configuration, source, mutate); + this.imageOperators.Add(op); return op; } public class FakeImageOperations : IInternalImageProcessingContext - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - private bool mutate; - - public FakeImageOperations(Image source, bool mutate) + public FakeImageOperations(Configuration configuration, Image source, bool mutate) { - this.mutate = mutate; + this.Configuration = configuration; this.Source = mutate ? source : source?.Clone(); } @@ -56,7 +54,9 @@ namespace SixLabors.ImageSharp.Tests.Processing public List Applied { get; } = new List(); - public MemoryAllocator MemoryAllocator => this.Source.GetConfiguration().MemoryAllocator; + public Configuration Configuration { get; } + + public IDictionary Properties { get; } = new Dictionary(); public Image GetResultImage() { @@ -71,25 +71,26 @@ namespace SixLabors.ImageSharp.Tests.Processing public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { this.Applied.Add(new AppliedOperation - { - Rectangle = rectangle, - NonGenericProcessor = processor - }); + { + Rectangle = rectangle, + NonGenericProcessor = processor + }); return this; } public IImageProcessingContext ApplyProcessor(IImageProcessor processor) { this.Applied.Add(new AppliedOperation - { - NonGenericProcessor = processor - }); + { + NonGenericProcessor = processor + }); return this; } public struct AppliedOperation { public Rectangle? Rectangle { get; set; } + public IImageProcessor GenericProcessor { get; set; } public IImageProcessor NonGenericProcessor { get; set; } diff --git a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs index e287fb7b5f..70c78ec81a 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -14,15 +14,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters { public class ColorBlindnessTest : BaseImageOperationsExtensionTest { - public static IEnumerable TheoryData = new[] { - new object[]{ new TestType(), ColorBlindnessMode.Achromatomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Achromatopsia }, - new object[]{ new TestType(), ColorBlindnessMode.Deuteranomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Deuteranopia }, - new object[]{ new TestType(), ColorBlindnessMode.Protanomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Protanopia }, - new object[]{ new TestType(), ColorBlindnessMode.Tritanomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Tritanopia } + public static IEnumerable TheoryData = new[] + { + new object[] { new TestType(), ColorBlindnessMode.Achromatomaly }, + new object[] { new TestType(), ColorBlindnessMode.Achromatopsia }, + new object[] { new TestType(), ColorBlindnessMode.Deuteranomaly }, + new object[] { new TestType(), ColorBlindnessMode.Deuteranopia }, + new object[] { new TestType(), ColorBlindnessMode.Protanomaly }, + new object[] { new TestType(), ColorBlindnessMode.Protanopia }, + new object[] { new TestType(), ColorBlindnessMode.Tritanomaly }, + new object[] { new TestType(), ColorBlindnessMode.Tritanopia } }; [Theory] @@ -33,6 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters this.operations.ColorBlindness(colorBlindness); this.Verify(); } + [Theory] [MemberData(nameof(TheoryData))] public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) diff --git a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs index e55e983dad..bf2d6823ad 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using Xunit; @@ -28,4 +28,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(1.5F, processor.Amount); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs index 08de55d6b1..9afaf16fd4 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -14,8 +14,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters { public class GrayscaleTest : BaseImageOperationsExtensionTest { - public static IEnumerable ModeTheoryData = new[] { - new object[]{ new TestType(), GrayscaleMode.Bt709 } + public static IEnumerable ModeTheoryData = new[] + { + new object[] { new TestType(), GrayscaleMode.Bt709 } }; [Theory] diff --git a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs new file mode 100644 index 0000000000..29d1d63e62 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Effects +{ + public class LightnessTest : BaseImageOperationsExtensionTest + { + [Fact] + public void Lightness_amount_LightnessProcessorDefaultsSet() + { + this.operations.Lightness(.5F); + LightnessProcessor processor = this.Verify(); + + Assert.Equal(.5F, processor.Amount); + } + + [Fact] + public void Lightness_amount_rect_LightnessProcessorDefaultsSet() + { + this.operations.Lightness(.5F, this.rect); + LightnessProcessor processor = this.Verify(this.rect); + + Assert.Equal(.5F, processor.Amount); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs index 65e04fbcc7..6cb38e2fe9 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs @@ -16,14 +16,16 @@ namespace SixLabors.ImageSharp.Tests public void Lomograph_amount_LomographProcessorDefaultsSet() { this.operations.Lomograph(); - this.Verify(); + var processor = this.Verify(); + Assert.Equal(processor.GraphicsOptions, this.options); } [Fact] public void Lomograph_amount_rect_LomographProcessorDefaultsSet() { this.operations.Lomograph(this.rect); - this.Verify(this.rect); + var processor = this.Verify(this.rect); + Assert.Equal(processor.GraphicsOptions, this.options); } } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs index c3e2c3c502..346df03799 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs @@ -14,14 +14,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters public void Polaroid_amount_PolaroidProcessorDefaultsSet() { this.operations.Polaroid(); - this.Verify(); + var processor = this.Verify(); + Assert.Equal(processor.GraphicsOptions, this.options); } [Fact] public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() { this.operations.Polaroid(this.rect); - this.Verify(this.rect); + var processor = this.Verify(this.rect); + Assert.Equal(processor.GraphicsOptions, this.options); } } } diff --git a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs index 1b5c16538a..0638441783 100644 --- a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs @@ -32,13 +32,15 @@ namespace SixLabors.ImageSharp.Tests.Processing var processorMock = new Mock(); this.processorDefinition = processorMock.Object; - this.image = new Image(new Configuration - { - ImageOperationsProvider = this.provider - }, 1, 1); + this.image = new Image( + new Configuration + { + ImageOperationsProvider = this.provider + }, + 1, + 1); } - [Fact] public void MutateCallsImageOperationsProvider_Func_OriginalImage() { @@ -106,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Processing [Fact] public void ApplyProcessors_ListOfProcessors_AppliesAllProcessorsToOperation() { - var operations = new FakeImageOperationsProvider.FakeImageOperations(null, false); + var operations = new FakeImageOperationsProvider.FakeImageOperations(Configuration.Default, null, false); operations.ApplyProcessors(this.processorDefinition); Assert.Contains(this.processorDefinition, operations.Applied.Select(x => x.NonGenericProcessor)); } diff --git a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs index 9d16583cd8..1c6a1c7ddc 100644 --- a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs @@ -5,7 +5,6 @@ using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing @@ -176,7 +175,7 @@ namespace SixLabors.ImageSharp.Tests.Processing } this.processorDefinition - .Setup(p => p.CreatePixelSpecificProcessor(It.IsAny>(), It.IsAny())) + .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) .Returns(this.regularProcessorImpl.Object); } @@ -189,11 +188,11 @@ namespace SixLabors.ImageSharp.Tests.Processing } this.cloningProcessorDefinition - .Setup(p => p.CreatePixelSpecificCloningProcessor(It.IsAny>(), It.IsAny())) + .Setup(p => p.CreatePixelSpecificCloningProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) .Returns(this.cloningProcessorImpl.Object); this.cloningProcessorDefinition - .Setup(p => p.CreatePixelSpecificProcessor(It.IsAny>(), It.IsAny())) + .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) .Returns(this.cloningProcessorImpl.Object); } } diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index c712325249..5e2b7062e7 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization [Theory] [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization [Theory] [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] public void Adaptive_TileInterpolation_10Tiles_WithClipping(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -120,11 +120,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization /// where it could happen that one too much start position was calculated in some cases. /// See: https://github.com/SixLabors/ImageSharp/pull/984 /// + /// The pixel type of the image. [Theory] - [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] - [WithTestPatternImages(170, 170, PixelTypes.Rgba32)] + [WithTestPatternImages(110, 110, PixelTypes.Rgb24)] + [WithTestPatternImages(170, 170, PixelTypes.Rgb24)] public void Issue984(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -133,10 +134,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, LuminanceLevels = 256, ClipHistogram = true, + ClipLimit = 5, NumberOfTiles = 10 }; image.Mutate(x => x.HistogramEqualization(options)); image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); } } } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index 978fd416bc..0336b231b5 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -1,11 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Overlays @@ -16,9 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_GlowProcessorWithDefaultValues() { this.operations.Glow(); - var p = this.Verify(); + GlowProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -26,10 +25,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays [Fact] public void Glow_Color_GlowProcessorWithDefaultValues() { - this.operations.Glow(Rgba32.Aquamarine); - var p = this.Verify(); + this.operations.Glow(Color.Aquamarine); + GlowProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Aquamarine, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -38,9 +37,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Glow_Radux_GlowProcessorWithDefaultValues() { this.operations.Glow(3.5f); - var p = this.Verify(); + GlowProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); } @@ -50,11 +49,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { var rect = new Rectangle(12, 123, 43, 65); this.operations.Glow(rect); - var p = this.Verify(rect); + GlowProcessor p = this.Verify(rect); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs index 2484cf0cb8..5d41c58cea 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.Primitives; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Overlays @@ -15,9 +14,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_VignetteProcessorWithDefaultValues() { this.operations.Vignette(); - var p = this.Verify(); + VignetteProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); @@ -27,9 +26,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_Color_VignetteProcessorWithDefaultValues() { this.operations.Vignette(Color.Aquamarine); - var p = this.Verify(); + VignetteProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Aquamarine, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); @@ -39,9 +38,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays public void Vignette_Radux_VignetteProcessorWithDefaultValues() { this.operations.Vignette(3.5f, 12123f); - var p = this.Verify(); + VignetteProcessor p = this.Verify(); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); @@ -52,12 +51,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { var rect = new Rectangle(12, 123, 43, 65); this.operations.Vignette(rect); - var p = this.Verify(rect); + VignetteProcessor p = this.Verify(rect); - Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(this.options, p.GraphicsOptions); Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index d3507ed4c4..e718df4a26 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -1,15 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { public class BinaryDitherTests @@ -19,38 +18,38 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public static readonly TheoryData OrderedDitherers = new TheoryData + public static readonly TheoryData OrderedDitherers = new TheoryData { - { "Bayer8x8", KnownDitherers.BayerDither8x8 }, - { "Bayer4x4", KnownDitherers.BayerDither4x4 }, - { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherers.BayerDither2x2 } + { "Bayer8x8", KnownDitherings.Bayer8x8 }, + { "Bayer4x4", KnownDitherings.Bayer4x4 }, + { "Ordered3x3", KnownDitherings.Ordered3x3 }, + { "Bayer2x2", KnownDitherings.Bayer2x2 } }; - public static readonly TheoryData ErrorDiffusers = new TheoryData + public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "Atkinson", KnownDiffusers.Atkinson }, - { "Burks", KnownDiffusers.Burks }, - { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, - { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, - { "Sierra2", KnownDiffusers.Sierra2 }, - { "Sierra3", KnownDiffusers.Sierra3 }, - { "SierraLite", KnownDiffusers.SierraLite }, - { "StevensonArce", KnownDiffusers.StevensonArce }, - { "Stucki", KnownDiffusers.Stucki }, + { "Atkinson", KnownDitherings.Atkinson }, + { "Burks", KnownDitherings.Burks }, + { "FloydSteinberg", KnownDitherings.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDitherings.JarvisJudiceNinke }, + { "Sierra2", KnownDitherings.Sierra2 }, + { "Sierra3", KnownDitherings.Sierra3 }, + { "SierraLite", KnownDitherings.SierraLite }, + { "StevensonArce", KnownDitherings.StevensonArce }, + { "Stucki", KnownDitherings.Stucki }, }; public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, PixelTypes.Rgba32)] - public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) - where TPixel : struct, IPixel + public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IDither ditherer) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -62,12 +61,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, PixelTypes.Rgba32)] - public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IErrorDiffuser diffuser) - where TPixel : struct, IPixel + public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IDither diffuser) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.Mutate(x => x.BinaryDither(diffuser)); image.DebugSave(provider, name); } } @@ -75,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -87,11 +86,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); image.DebugSave(provider); } } @@ -99,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (Image image = source.Clone()) @@ -116,18 +115,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (Image image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); image.DebugSave(provider); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index 4ae5d60513..3801a48883 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -1,10 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization @@ -19,18 +17,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization .25F, .75F }; - + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - + public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -42,19 +40,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) using (var image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); image.Mutate(x => x.BinaryThreshold(value, bounds)); - image.DebugSave(provider, value); + image.DebugSave(provider, value); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs index 0a10d0755f..bd574c916b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -1,10 +1,9 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; @@ -14,9 +13,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public abstract class Basic1ParameterConvolutionTests { private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); - + public static readonly TheoryData Values = new TheoryData { 3, 5 }; - + public static readonly string[] InputImages = { TestImages.Bmp.Car, @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] public void OnFullImage(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunValidatingProcessorTest( @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunRectangleConstrainedValidatingProcessorTest( @@ -48,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } protected abstract void Apply(IImageProcessingContext ctx, int value); - + protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index 296b8d1d7f..9dc1350166 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -6,13 +6,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; - +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.Primitives; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -58,8 +57,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution // Make sure the kernel components are the same using (var image = new Image(1, 1)) { + Configuration configuration = image.GetConfiguration(); var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); - using (var processor = (BokehBlurProcessor)definition.CreatePixelSpecificProcessor(image, image.Bounds())) + using (var processor = (BokehBlurProcessor)definition.CreatePixelSpecificProcessor(configuration, image, image.Bounds())) { Assert.Equal(components.Count, processor.Kernels.Count); foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b))) @@ -123,39 +123,81 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)] public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - provider.RunValidatingProcessorTest( - x => x.BokehBlur(value.Radius, value.Components, value.Gamma), - testOutputDetails: value.ToString(), - appendPixelTypeToFileName: false); + static void RunTest(string providerDump, string infoDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + BokehBlurInfo value = BasicSerializer.Deserialize(infoDump); + + provider.RunValidatingProcessorTest( + x => x.BokehBlur(value.Radius, value.Components, value.Gamma), + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + } + + RemoteExecutor + .Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value)) + .Dispose(); } [Theory] - [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.Gray8)] + /* + TODO: Re-enable L8 when we update the reference images. + [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.L8)] + */ + [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32)] public void BokehBlurFilterProcessor_WorksWithAllPixelTypes(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - provider.RunValidatingProcessorTest( - x => x.BokehBlur(8, 2, 3), - appendSourceFileOrDescription: false); - } + static void RunTest(string providerDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + provider.RunValidatingProcessorTest( + x => x.BokehBlur(8, 2, 3), + appendSourceFileOrDescription: false); + } + RemoteExecutor + .Invoke(RunTest, BasicSerializer.Serialize(provider)) + .Dispose(); + } [Theory] [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - provider.RunValidatingProcessorTest( - x => + static void RunTest(string providerDump, string infoDump) + { + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + BokehBlurInfo value = BasicSerializer.Deserialize(infoDump); + + provider.RunValidatingProcessorTest( + x => { Size size = x.GetCurrentSize(); var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); }, - testOutputDetails: value.ToString(), - appendPixelTypeToFileName: false); + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + } + + RemoteExecutor + .Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value)) + .Dispose(); + } + + [Theory] + [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(41, c => c.BokehBlur()); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index a7cf9360cf..66e9ba2df3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 05524b20b0..3d1e378b16 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -1,24 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { [GroupOutput("Convolution")] public class DetectEdgesTest { - // I think our comparison is not accurate enough (nor can be) for RgbaVector. - // The image pixels are identical according to BeyondCompare. - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); + + private static readonly ImageComparer TransparentComparer = ImageComparer.TolerantPercentage(0.5F); public static readonly string[] TestImages = { Tests.TestImages.Png.Bike }; - + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public static readonly TheoryData DetectEdgesFilters = new TheoryData @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTestOnWrappedMemoryImage( ctx => @@ -47,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); ctx.DetectEdges(bounds); }, - comparer: ValidatorComparer, + comparer: OpaqueComparer, useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); } @@ -55,33 +54,42 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectionOperators detector) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges(detector)); image.DebugSave(provider, detector.ToString()); - image.CompareToReferenceOutput(ValidatorComparer, provider, detector.ToString()); + image.CompareToReferenceOutput(comparer, provider, detector.ToString()); } } [Theory] [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + // James: + // I think our comparison is not accurate enough (nor can be) for RgbaVector. + // The image pixels are identical according to BeyondCompare. + ImageComparer comparer = typeof(TPixel) == typeof(RgbaVector) ? + ImageComparer.TolerantPercentage(1f) : + OpaqueComparer; + using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges()); image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); + image.CompareToReferenceOutput(comparer, provider); } } [Theory] [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -93,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -101,8 +109,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.DetectEdges(bounds)); image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); + image.CompareToReferenceOutput(OpaqueComparer, provider); } } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers(TestImageProvider provider, EdgeDetectionOperators detector) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 24ed090d8a..d1a3baa5a3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index 96d223ee9f..535520cb8b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 5417bc1f34..2ae9263926 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -15,34 +14,37 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24 | PixelTypes.RgbaVector; - + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public static readonly TheoryData ErrorDiffusers = new TheoryData - { - KnownDiffusers.Atkinson, - KnownDiffusers.Burks, - KnownDiffusers.FloydSteinberg, - KnownDiffusers.JarvisJudiceNinke, - KnownDiffusers.Sierra2, - KnownDiffusers.Sierra3, - KnownDiffusers.SierraLite, - KnownDiffusers.StevensonArce, - KnownDiffusers.Stucki, - }; - - public static readonly TheoryData OrderedDitherers = new TheoryData - { - KnownDitherers.BayerDither8x8, - KnownDitherers.BayerDither4x4, - KnownDitherers.OrderedDither3x3, - KnownDitherers.BayerDither2x2 - }; + public static readonly TheoryData ErrorDiffusers + = new TheoryData + { + { KnownDitherings.Atkinson, nameof(KnownDitherings.Atkinson) }, + { KnownDitherings.Burks, nameof(KnownDitherings.Burks) }, + { KnownDitherings.FloydSteinberg, nameof(KnownDitherings.FloydSteinberg) }, + { KnownDitherings.JarvisJudiceNinke, nameof(KnownDitherings.JarvisJudiceNinke) }, + { KnownDitherings.Sierra2, nameof(KnownDitherings.Sierra2) }, + { KnownDitherings.Sierra3, nameof(KnownDitherings.Sierra3) }, + { KnownDitherings.SierraLite, nameof(KnownDitherings.SierraLite) }, + { KnownDitherings.StevensonArce, nameof(KnownDitherings.StevensonArce) }, + { KnownDitherings.Stucki, nameof(KnownDitherings.Stucki) }, + }; + + public static readonly TheoryData OrderedDitherers + = new TheoryData + { + { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, + { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, + { KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) }, + { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } + }; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - - private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; + + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; /// /// The output is visually correct old 32bit runtime, @@ -54,28 +56,28 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Diffuse(DefaultErrorDiffuser, .5F, rect), + (x, rect) => x.Dither(DefaultErrorDiffuser, rect), comparer: ValidatorComparer); } [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.Dither(DefaultDitherer, rect), comparer: ValidatorComparer); @@ -84,33 +86,34 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + // Increased tolerance because of compatibility issues on .NET 4.6.2: var comparer = ImageComparer.TolerantPercentage(1f); - provider.RunValidatingProcessorTest(x => x.Diffuse(DefaultErrorDiffuser, 0.5f), comparer: comparer); + provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); } [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] public void DiffusionFilter_WorksWithAllErrorDiffusers( TestImageProvider provider, - IErrorDiffuser diffuser) - where TPixel : struct, IPixel + IDither diffuser, + string name) + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunValidatingProcessorTest( - x => x.Diffuse(diffuser, 0.5f), - testOutputDetails: diffuser.GetType().Name, + x => x.Dither(diffuser), + testOutputDetails: name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); } @@ -118,13 +121,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunValidatingProcessorTest( x => x.Dither(DefaultDitherer), comparer: ValidatorComparer); @@ -134,19 +137,41 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] public void DitherFilter_WorksWithAllDitherers( TestImageProvider provider, - IOrderedDither ditherer) - where TPixel : struct, IPixel + IDither ditherer, + string name) + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunValidatingProcessorTest( x => x.Dither(ditherer), - testOutputDetails: ditherer.GetType().Name, + testOutputDetails: name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); } + + [Theory] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(OrderedDither.Ordered3x3))] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(ErrorDither.FloydSteinberg))] + public void CommonDitherers_WorkWithDiscoBuffers( + TestImageProvider provider, + string name) + where TPixel : unmanaged, IPixel + { + IDither dither = TestUtils.GetDither(name); + if (SkipAllDitherTests) + { + return; + } + + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.Dither(dither), + name, + ImageComparer.TolerantPercentage(0.001f)); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index 56ffceb47a..88ebec4e2c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,22 +16,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects TestImages.Png.Splash, TestImages.Png.Ducky }; - + [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); + provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); } [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.BackgroundColor(Color.HotPink, rect)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index aad48e357a..4eeebc3a0c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; - +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects @@ -13,9 +13,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { public static readonly TheoryData OilPaintValues = new TheoryData { - { 15, 10 }, + { 15, 10 }, { 6, 5 } }; + public static readonly string[] InputImages = { TestImages.Png.CalliphoraPartial, @@ -25,11 +26,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider, int levels, int brushSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( - x => x.OilPaint(levels, brushSize), - $"{levels}-{brushSize}", + x => + { + x.OilPaint(levels, brushSize); + return $"{levels}-{brushSize}"; + }, + ImageComparer.TolerantPercentage(0.01F), appendPixelTypeToFileName: false); } @@ -37,11 +42,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(OilPaintValues), 100, 100, PixelTypes.Rgba32)] public void InBox(TestImageProvider provider, int levels, int brushSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.OilPaint(levels, brushSize, rect), - $"{levels}-{brushSize}"); + $"{levels}-{brushSize}", + ImageComparer.TolerantPercentage(0.01F)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs new file mode 100644 index 0000000000..dd4abfc76a --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +{ + [GroupOutput("Effects")] + public class PixelShaderTest + { + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest( + x => x.ProcessPixelRowsAsVector4( + span => + { + for (int i = 0; i < span.Length; i++) + { + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + span[i] = new Vector4(avg); + } + }), + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.ProcessPixelRowsAsVector4( + span => + { + for (int i = 0; i < span.Length; i++) + { + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + span[i] = new Vector4(avg); + } + }, rect)); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void PositionAwareFullImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest( + c => c.ProcessPixelRowsAsVector4( + (span, offset) => + { + int y = offset.Y; + int x = offset.X; + for (int i = 0; i < span.Length; i++) + { + float + sine = MathF.Sin(y), + cosine = MathF.Cos(x + i), + sum = sine + cosine, + abs = MathF.Abs(sum), + a = 0.5f + (abs / 2); // Max value for sin(y) + cos(x) is 2 + + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + var gray = new Vector4(avg, avg, avg, a); + + span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); + } + }), + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void PositionAwareInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (c, rect) => c.ProcessPixelRowsAsVector4( + (span, offset) => + { + int y = offset.Y; + int x = offset.X; + for (int i = 0; i < span.Length; i++) + { + float + sine = MathF.Sin(y), + cosine = MathF.Cos(x + i), + sum = sine + cosine, + abs = MathF.Abs(sum), + a = 0.5f + (abs / 2); + + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + var gray = new Vector4(avg, avg, avg, a); + + span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); + } + }, rect)); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index e95452ffb7..d7cee311d1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); } @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => x.Pixelate(value, rect), value); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs index 64aeae0534..86518b015a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyBlackWhiteFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs index 54a8dd4b7d..bb52731bb7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.5F + 1.5F }; [Theory] [WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)] public void ApplyBrightnessFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs index 8ac56655ea..5c6a298225 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -31,6 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)] public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) - where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs index e5e4fa4a90..1758db8dd8 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.5F + 1.5F }; [Theory] [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] public void ApplyContrastFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Contrast(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index 68daa80eac..ae9abed7f6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,7 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Primitives; + using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; [GroupOutput("Filters")] @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void ApplyFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ColorMatrix m = CreateCombinedTestFilterMatrix(); @@ -30,13 +30,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ColorMatrix m = CreateCombinedTestFilterMatrix(); provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); } + [Theory] + [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] + public void FilterProcessor_WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + ColorMatrix m = CreateCombinedTestFilterMatrix(); + + provider.RunBufferCapacityLimitProcessorTest(37, c => c.Filter(m)); + } + private static ColorMatrix CreateCombinedTestFilterMatrix() { ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); @@ -44,6 +54,5 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); return brightness * hue * saturation; } - } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs index c2728e0435..2352a4dce7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -22,12 +22,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters /// /// Use test patterns over loaded images to save decode time. /// + /// The pixel type of the image. [Theory] [WithTestPatternImages(nameof(GrayscaleModeTypes), 48, 48, PixelTypes.Rgba32)] public void ApplyGrayscaleFilter(TestImageProvider provider, GrayscaleMode value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Grayscale(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs index 4ce700bad0..9b96653b88 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters = new TheoryData { 180, - -180 + -180 }; [Theory] [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] public void ApplyHueFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Hue(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs index 1b4c70646a..cb7a403f91 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyInvertFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Invert()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs index b7b635c2d2..04e86c955b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyKodachromeFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Kodachrome()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs new file mode 100644 index 0000000000..8b77f902df --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +{ + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + [GroupOutput("Filters")] + public class LightnessTest + { + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); + + public static readonly TheoryData LightnessValues + = new TheoryData + { + .5F, + 1.5F + }; + + [Theory] + [WithTestPatternImages(nameof(LightnessValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyLightnessFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Lightness(value), value, this.imageComparer); + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs index 013ec38740..65e616dca3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyLomographFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Lomograph()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs index 35e405f4c9..bd73361192 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,16 +15,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects public static readonly TheoryData AlphaValues = new TheoryData { - 20/100F, - 80/100F + 20 / 100F, + 80 / 100F }; [Theory] [WithTestPatternImages(nameof(AlphaValues), 48, 48, PixelTypes.Rgba32)] public void ApplyAlphaFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Opacity(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs index 3b39542a55..26dac75321 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyPolaroidFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Polaroid()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs index 31fab8b65d..4be11a72cc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters = new TheoryData { .5F, - 1.5F, + 1.5F, }; [Theory] [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] public void ApplySaturationFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Saturate(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs index b7d381f5f2..fa43cb15af 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplySepiaFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Sepia()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index 5462a8b2da..e6a960f9e2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs index c1c6bbd7cf..88cd6688a5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; @@ -22,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays [Theory] [WithFileCollection(nameof(InputImages), nameof(ColorNames), PixelTypes.Rgba32)] public void FullImage_ApplyColor(TestImageProvider provider, string colorName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; Color color = TestUtils.GetColorByName(colorName); @@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void FullImage_ApplyRadius(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunValidatingProcessorTest( @@ -49,12 +48,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunRectangleConstrainedValidatingProcessorTest(this.Apply); } + [Theory] + [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(37, c => this.Apply(c, Color.DarkRed)); + } + protected abstract void Apply(IImageProcessingContext ctx, Color color); protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index 9448feefe2..470f48f781 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index b3900325db..bb7921d686 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void OctreeQuantizerConstructor() { - var quantizer = new OctreeQuantizer(128); - - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); - - quantizer = new OctreeQuantizer(false); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Diffuser); - - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); - - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson, 128); - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new OctreeQuantizer(expected); + + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = null }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] @@ -38,21 +42,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(false); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index f2e1136ca6..3c1fa11ab0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -10,61 +10,70 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { public class PaletteQuantizerTests { - private static readonly Color[] Rgb = new Color[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue }; + private static readonly Color[] Palette = new Color[] { Color.Red, Color.Green, Color.Blue }; [Fact] public void PaletteQuantizerConstructor() { - var quantizer = new PaletteQuantizer(Rgb); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, false); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Null(quantizer.Diffuser); + expected = new QuantizerOptions { Dither = null }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] public void PaletteQuantizerCanCreateFrameQuantizer() { - var quantizer = new PaletteQuantizer(Rgb); + var quantizer = new PaletteQuantizer(Palette); IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, false); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } [Fact] public void KnownQuantizersWebSafeTests() { IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } [Fact] public void KnownQuantizersWernerTests() { IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs new file mode 100644 index 0000000000..bcbb60e798 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -0,0 +1,220 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +{ + public class QuantizerTests + { + /// + /// Something is causing tests to fail on NETFX in CI. + /// Could be a JIT error as everything runs well and is identical to .NET Core output. + /// Not worth investigating for now. + /// + /// + private static readonly bool SkipAllQuantizerTests = TestEnvironment.IsFramework; + + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, + TestImages.Png.Bike + }; + + private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null }; + private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg }; + private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.Bayer8x8 }; + + private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .75F + }; + + private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .75F + }; + + public static readonly TheoryData Quantizers + = new TheoryData + { + // Known uses error diffusion by default. + KnownQuantizers.Octree, + KnownQuantizers.WebSafe, + KnownQuantizers.Werner, + KnownQuantizers.Wu, + new OctreeQuantizer(NoDitherOptions), + new WebSafePaletteQuantizer(NoDitherOptions), + new WernerPaletteQuantizer(NoDitherOptions), + new WuQuantizer(NoDitherOptions), + new OctreeQuantizer(OrderedDitherOptions), + new WebSafePaletteQuantizer(OrderedDitherOptions), + new WernerPaletteQuantizer(OrderedDitherOptions), + new WuQuantizer(OrderedDitherOptions) + }; + + public static readonly TheoryData DitherScaleQuantizers + = new TheoryData + { + new OctreeQuantizer(Diffuser0_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), + new WuQuantizer(Diffuser0_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + new WuQuantizer(Diffuser0_25_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + new WuQuantizer(Diffuser0_5_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + new WuQuantizer(Diffuser0_75_ScaleDitherOptions), + + new OctreeQuantizer(DiffuserDitherOptions), + new WebSafePaletteQuantizer(DiffuserDitherOptions), + new WernerPaletteQuantizer(DiffuserDitherOptions), + new WuQuantizer(DiffuserDitherOptions), + + new OctreeQuantizer(Ordered0_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), + new WuQuantizer(Ordered0_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_25_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), + new WuQuantizer(Ordered0_25_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_5_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), + new WuQuantizer(Ordered0_5_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_75_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), + new WuQuantizer(Ordered0_75_ScaleDitherOptions), + + new OctreeQuantizer(OrderedDitherOptions), + new WebSafePaletteQuantizer(OrderedDitherOptions), + new WernerPaletteQuantizer(OrderedDitherOptions), + new WuQuantizer(OrderedDitherOptions), + }; + + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantizationInBox(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Quantize(quantizer, rect), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantization(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; + + provider.RunValidatingProcessorTest( + x => x.Quantize(quantizer), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Png.David, nameof(DitherScaleQuantizers), PixelTypes.Rgba32)] + public void ApplyQuantizationWithDitheringScale(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither.GetType().Name; + float ditherScale = quantizer.Options.DitherScale; + string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherScale}"); + + provider.RunValidatingProcessorTest( + x => x.Quantize(quantizer), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 625043c7f1..eb9d738e9a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void WuQuantizerConstructor() { - var quantizer = new WuQuantizer(128); - - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); - - quantizer = new WuQuantizer(false); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Diffuser); - - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); - - quantizer = new WuQuantizer(KnownDiffusers.Atkinson, 128); - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new WuQuantizer(expected); + + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = null }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] @@ -38,21 +42,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(false); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs new file mode 100644 index 0000000000..2de903d664 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -0,0 +1,265 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Reflection; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Processing.Transforms +{ + public class AffineTransformTests + { + private readonly ITestOutputHelper output; + + // 1 byte difference on one color component. + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0134F, 3); + + /// + /// angleDeg, sx, sy, tx, ty + /// + public static readonly TheoryData TransformValues + = new TheoryData + { + { 0, 1, 1, 0, 0 }, + { 50, 1, 1, 0, 0 }, + { 0, 1, 1, 20, 10 }, + { 50, 1, 1, 20, 10 }, + { 0, 1, 1, -20, -10 }, + { 50, 1, 1, -20, -10 }, + { 50, 1.5f, 1.5f, 0, 0 }, + { 50, 1.1F, 1.3F, 30, -20 }, + { 0, 2f, 1f, 0, 0 }, + { 0, 1f, 2f, 0, 0 }, + }; + + public static readonly TheoryData ResamplerNames = new TheoryData + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; + + public static readonly TheoryData Transform_DoesNotCreateEdgeArtifacts_ResamplerNames = + new TheoryData + { + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Lanczos8), + }; + + public AffineTransformTests(ITestOutputHelper output) => this.output = output; + + /// + /// The output of an "all white" image should be "all white" or transparent, regardless of the transformation and the resampler. + /// + /// The pixel type of the image. + [Theory] + [WithSolidFilledImages(nameof(Transform_DoesNotCreateEdgeArtifacts_ResamplerNames), 5, 5, 255, 255, 255, 255, PixelTypes.Rgba32)] + public void Transform_DoesNotCreateEdgeArtifacts(TestImageProvider provider, string resamplerName) + where TPixel : unmanaged, IPixel + { + IResampler resampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(30); + + image.Mutate(c => c.Transform(builder, resampler)); + image.DebugSave(provider, resamplerName); + + VerifyAllPixelsAreWhiteOrTransparent(image); + } + } + + [Theory] + [WithTestPatternImages(nameof(TransformValues), 100, 50, PixelTypes.Rgba32)] + public void Transform_RotateScaleTranslate( + TestImageProvider provider, + float angleDeg, + float sx, + float sy, + float tx, + float ty) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, $"_original"); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(angleDeg) + .AppendScale(new SizeF(sx, sy)) + .AppendTranslation(new PointF(tx, ty)); + + this.PrintMatrix(builder.BuildMatrix(image.Size())); + + image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); + + FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})"; + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); + } + } + + [Theory] + [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] + public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(angleDeg) + .AppendScale(new SizeF(s, s)); + + image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); + + FormattableString testOutputDetails = $"R({angleDeg})_S({s})"; + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); + } + } + + public static readonly TheoryData Transform_IntoRectangle_Data = + new TheoryData + { + { 0, 0, 10, 10 }, + { 0, 0, 5, 10 }, + { 0, 0, 10, 5 }, + { 5, 0, 5, 10 }, + { -5, -5, 20, 20 } + }; + + /// + /// Testing transforms using custom source rectangles: + /// https://github.com/SixLabors/ImageSharp/pull/386#issuecomment-357104963 + /// + /// The pixel type of the image. + [Theory] + [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] + public void Transform_FromSourceRectangle1(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var rectangle = new Rectangle(48, 0, 48, 24); + + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, $"_original"); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendScale(new SizeF(2, 1.5F)); + + image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } + + [Theory] + [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] + public void Transform_FromSourceRectangle2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var rectangle = new Rectangle(0, 24, 48, 24); + + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendScale(new SizeF(1F, 2F)); + + image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } + + [Theory] + [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithSampler(TestImageProvider provider, string resamplerName) + where TPixel : unmanaged, IPixel + { + IResampler sampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(50) + .AppendScale(new SizeF(.6F, .6F)); + + image.Mutate(i => i.Transform(builder, sampler)); + + image.DebugSave(provider, resamplerName); + image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); + } + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 21)] + public void WorksWithDiscoBuffers(TestImageProvider provider, int bufferCapacityInPixelRows) + where TPixel : unmanaged, IPixel + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(50) + .AppendScale(new SizeF(.6F, .6F)); + provider.RunBufferCapacityLimitProcessorTest( + bufferCapacityInPixelRows, + c => c.Transform(builder)); + } + + private static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No resampler named {name}"); + } + + return (IResampler)property.GetValue(null); + } + + private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) + where TPixel : unmanaged, IPixel + { + Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span data)); + var white = new Rgb24(255, 255, 255); + foreach (TPixel pixel in data) + { + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + if (rgba.A == 0) + { + continue; + } + + Assert.Equal(white, rgba.Rgb); + } + } + + private void PrintMatrix(Matrix3x2 a) + { + string s = $"{a.M11:F10},{a.M12:F10},{a.M21:F10},{a.M22:F10},{a.M31:F10},{a.M32:F10}"; + this.output.WriteLine(s); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index ceb6f8363b..a4fec9fd92 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -21,8 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { { ExifDataType.Byte, new byte[] { 1 } }, { ExifDataType.SignedByte, new byte[] { 2 } }, - { ExifDataType.SignedShort, BitConverter.GetBytes((short) 3) }, - { ExifDataType.Long, BitConverter.GetBytes((uint) 4) }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, + { ExifDataType.Long, BitConverter.GetBytes(4U) }, { ExifDataType.SignedLong, BitConverter.GetBytes(5) } }; @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -58,17 +58,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var profile = new ExifProfile(); profile.SetValue(ExifTag.JPEGTables, orientation); byte[] bytes = profile.ToByteArray(); + // Change the tag into ExifTag.Orientation bytes[16] = 18; bytes[17] = 1; + // Change the data type bytes[18] = (byte)dataType; + // Change the number of components bytes[20] = 1; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index 50217e892d..f0eef3afd1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -6,7 +6,6 @@ using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] [WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] public void Crop(TestImageProvider provider, int x, int y, int w, int h) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var rect = new Rectangle(x, y, w, h); FormattableString info = $"X{x}Y{y}.W{w}H{h}"; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index d20e6fa359..6ba795e560 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -22,9 +22,28 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] public void EntropyCrop(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); } + + [Theory] + [WithBlankImages(40, 30, PixelTypes.Rgba32)] + [WithBlankImages(30, 40, PixelTypes.Rgba32)] + public void Entropy_WillNotCropWhiteImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image image = provider.GetImage(); + var expectedHeight = image.Height; + var expectedWidth = image.Width; + + // act + image.Mutate(img => img.EntropyCrop()); + + // assert + Assert.Equal(image.Width, expectedWidth); + Assert.Equal(image.Height, expectedHeight); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index 3c932bfaa6..ae53afd67f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { [GroupOutput("Transforms")] @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] public void Flip(TestImageProvider provider, FlipMode flipMode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( ctx => ctx.Flip(flipMode), @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] public void Flip_WorksOnWrappedMemoryImage(TestImageProvider provider, FlipMode flipMode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTestOnWrappedMemoryImage( ctx => ctx.Flip(flipMode), @@ -47,4 +46,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms appendPixelTypeToFileName: false); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs index dbaff43f0c..28833248c0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void ImageShouldPad(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var color = Color.Red; TPixel expected = color.ToPixel(); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs index b7b4597c79..d3025d9118 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -9,7 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { public class ResamplerTests - { + { [Theory] [InlineData(-2, 0)] [InlineData(-1, 0)] @@ -66,4 +66,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms Assert.Equal(result, expected); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs index b351ec235f..cdc96f042a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; using Xunit; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 1681c3046a..3d08cf1a4c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -26,7 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + where TResampler : struct, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms TolerantMath tolerantMath = TolerantMath.Default; double radius = tolerantMath.Ceiling(scale * sampler.Radius); - + var result = new List(); for (int i = 0; i < destinationSize; i++) @@ -61,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms double sum = 0; - var values = new double[right - left + 1]; + double[] values = new double[right - left + 1]; for (int j = left; j <= right; j++) { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 91b011ed6a..8dbc056550 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -25,91 +25,81 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms /// /// resamplerName, srcSize, destSize /// - public static readonly TheoryData KernelMapData = new TheoryData + public static readonly TheoryData KernelMapData + = new TheoryData { - { nameof(KnownResamplers.Bicubic), 15, 10 }, - { nameof(KnownResamplers.Bicubic), 10, 15 }, - { nameof(KnownResamplers.Bicubic), 20, 20 }, - { nameof(KnownResamplers.Bicubic), 50, 40 }, - { nameof(KnownResamplers.Bicubic), 40, 50 }, - { nameof(KnownResamplers.Bicubic), 500, 200 }, - { nameof(KnownResamplers.Bicubic), 200, 500 }, - { nameof(KnownResamplers.Bicubic), 3032, 400 }, - - { nameof(KnownResamplers.Bicubic), 10, 25 }, - - { nameof(KnownResamplers.Lanczos3), 16, 12 }, - { nameof(KnownResamplers.Lanczos3), 12, 16 }, - { nameof(KnownResamplers.Lanczos3), 12, 9 }, - { nameof(KnownResamplers.Lanczos3), 9, 12 }, - { nameof(KnownResamplers.Lanczos3), 6, 8 }, - { nameof(KnownResamplers.Lanczos3), 8, 6 }, - { nameof(KnownResamplers.Lanczos3), 20, 12 }, - - { nameof(KnownResamplers.Lanczos3), 5, 25 }, - { nameof(KnownResamplers.Lanczos3), 5, 50 }, - - { nameof(KnownResamplers.Lanczos3), 25, 5 }, - { nameof(KnownResamplers.Lanczos3), 50, 5 }, - { nameof(KnownResamplers.Lanczos3), 49, 5 }, - { nameof(KnownResamplers.Lanczos3), 31, 5 }, - - { nameof(KnownResamplers.Lanczos8), 500, 200 }, - { nameof(KnownResamplers.Lanczos8), 100, 10 }, - { nameof(KnownResamplers.Lanczos8), 100, 80 }, - { nameof(KnownResamplers.Lanczos8), 10, 100 }, + { KnownResamplers.Bicubic, 15, 10 }, + { KnownResamplers.Bicubic, 10, 15 }, + { KnownResamplers.Bicubic, 20, 20 }, + { KnownResamplers.Bicubic, 50, 40 }, + { KnownResamplers.Bicubic, 40, 50 }, + { KnownResamplers.Bicubic, 500, 200 }, + { KnownResamplers.Bicubic, 200, 500 }, + { KnownResamplers.Bicubic, 3032, 400 }, + { KnownResamplers.Bicubic, 10, 25 }, + { KnownResamplers.Lanczos3, 16, 12 }, + { KnownResamplers.Lanczos3, 12, 16 }, + { KnownResamplers.Lanczos3, 12, 9 }, + { KnownResamplers.Lanczos3, 9, 12 }, + { KnownResamplers.Lanczos3, 6, 8 }, + { KnownResamplers.Lanczos3, 8, 6 }, + { KnownResamplers.Lanczos3, 20, 12 }, + { KnownResamplers.Lanczos3, 5, 25 }, + { KnownResamplers.Lanczos3, 5, 50 }, + { KnownResamplers.Lanczos3, 25, 5 }, + { KnownResamplers.Lanczos3, 50, 5 }, + { KnownResamplers.Lanczos3, 49, 5 }, + { KnownResamplers.Lanczos3, 31, 5 }, + { KnownResamplers.Lanczos8, 500, 200 }, + { KnownResamplers.Lanczos8, 100, 10 }, + { KnownResamplers.Lanczos8, 100, 80 }, + { KnownResamplers.Lanczos8, 10, 100 }, // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: - { nameof(KnownResamplers.Box), 378, 149 }, - { nameof(KnownResamplers.Box), 349, 174 }, + { KnownResamplers.Box, 378, 149 }, + { KnownResamplers.Box, 349, 174 }, // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData - { nameof(KnownResamplers.Box), 201, 100 }, - { nameof(KnownResamplers.Box), 199, 99 }, - { nameof(KnownResamplers.Box), 10, 299 }, - { nameof(KnownResamplers.Box), 299, 10 }, - { nameof(KnownResamplers.Box), 301, 300 }, - { nameof(KnownResamplers.Box), 1180, 480 }, - - { nameof(KnownResamplers.Lanczos2), 3264, 3032 }, - - { nameof(KnownResamplers.Bicubic), 1280, 2240 }, - { nameof(KnownResamplers.Bicubic), 1920, 1680 }, - { nameof(KnownResamplers.Bicubic), 3072, 2240 }, - - { nameof(KnownResamplers.Welch), 300, 2008 }, + { KnownResamplers.Box, 201, 100 }, + { KnownResamplers.Box, 199, 99 }, + { KnownResamplers.Box, 10, 299 }, + { KnownResamplers.Box, 299, 10 }, + { KnownResamplers.Box, 301, 300 }, + { KnownResamplers.Box, 1180, 480 }, + { KnownResamplers.Lanczos2, 3264, 3032 }, + { KnownResamplers.Bicubic, 1280, 2240 }, + { KnownResamplers.Bicubic, 1920, 1680 }, + { KnownResamplers.Bicubic, 3072, 2240 }, + { KnownResamplers.Welch, 300, 2008 }, // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData - { nameof(KnownResamplers.Bicubic), 10, 50 }, - { nameof(KnownResamplers.Bicubic), 49, 301 }, - { nameof(KnownResamplers.Bicubic), 301, 49 }, - { nameof(KnownResamplers.Bicubic), 1680, 1200 }, - { nameof(KnownResamplers.Box), 13, 299 }, - { nameof(KnownResamplers.Lanczos5), 3032, 600 }, + { KnownResamplers.Bicubic, 10, 50 }, + { KnownResamplers.Bicubic, 49, 301 }, + { KnownResamplers.Bicubic, 301, 49 }, + { KnownResamplers.Bicubic, 1680, 1200 }, + { KnownResamplers.Box, 13, 299 }, + { KnownResamplers.Lanczos5, 3032, 600 }, }; public static TheoryData GeneratedImageResizeData = GenerateImageResizeData(); - - [Theory( - Skip = "Only for debugging and development" - )] + [Theory(Skip = "Only for debugging and development")] [MemberData(nameof(KernelMapData))] - public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize) + public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); + var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); } [Theory] [MemberData(nameof(KernelMapData))] - public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); } // Comprehensive but expensive tests, for ResizeKernelMap. @@ -124,14 +114,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } #endif - private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); - var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); - - + var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); + var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #if DEBUG this.Output.WriteLine(kernelMap.Info); @@ -157,8 +144,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms Assert.Equal(expectedValues.Length, actualValues.Length); - - for (int x = 0; x < expectedValues.Length; x++) { Assert.True( @@ -168,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - private static string PrintKernelMap(ResizeKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ResizeKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap(ReferenceKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ReferenceKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); private static string PrintKernelMap( TKernelMap kernelMap, @@ -207,7 +192,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms return bld.ToString(); } - private static TheoryData GenerateImageResizeData() { var result = new TheoryData(); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 43384aee7c..3f323000ab 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -5,19 +5,15 @@ using System; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; - using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { public class ResizeTests @@ -39,15 +35,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms nameof(KnownResamplers.Lanczos5), }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); [Fact] public void Resize_PixelAgnostic() { - var filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + string filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); - using (Image image = Image.Load(filePath)) + using (var image = Image.Load(filePath)) { image.Mutate(x => x.Resize(image.Size() / 2)); string path = System.IO.Path.Combine( @@ -58,14 +53,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - [Theory( - Skip = "Debug only, enable manually" - )] + [Theory(Skip = "Debug only, enable manually")] [WithTestPatternImages(4000, 4000, PixelTypes.Rgba32, 300, 1024)] [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 1024)] [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 128)] public void LargeImage(TestImageProvider provider, int destSize, int workingBufferSizeHintInKilobytes) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -74,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; - using (var image = provider.GetImage()) + using (Image image = provider.GetImage()) { image.Mutate(x => x.Resize(destSize, destSize)); image.DebugSave(provider, appendPixelTypeToFileName: false); @@ -86,14 +79,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithBasicTestPatternImages(2, 256, PixelTypes.Rgba32, 1, 1, 1, 8)] [WithBasicTestPatternImages(2, 32, PixelTypes.Rgba32, 1, 1, 1, 2)] public void Resize_BasicSmall(TestImageProvider provider, int wN, int wD, int hN, int hD) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Basic test case, very helpful for debugging // [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] means: // resizing: (15, 12) -> (10, 6) // kernel dimensions: (3, 4) - - using (Image image = provider.GetImage()) { var destSize = new Size(image.Width * wN / wD, image.Height * hN / hD); @@ -117,25 +108,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( TestImageProvider provider, int workingBufferLimitInRows) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image0 = provider.GetImage()) { Size destSize = image0.Size() / 4; - Configuration configuration = Configuration.CreateDefaultInstance(); + var configuration = Configuration.CreateDefaultInstance(); int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; - TestMemoryAllocator allocator = new TestMemoryAllocator(); + var allocator = new TestMemoryAllocator(); configuration.MemoryAllocator = allocator; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - var verticalKernelMap = ResizeKernelMap.Calculate( - KnownResamplers.Bicubic, + var verticalKernelMap = ResizeKernelMap.Calculate( + default, destSize.Height, image0.Height, Configuration.Default.MemoryAllocator); - int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; + int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; verticalKernelMap.Dispose(); using (Image image = image0.Clone(configuration)) @@ -162,10 +153,43 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 100)] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 31, 73)] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 73, 31)] + [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 13, 17)] + [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 79, 23)] + [WithTestPatternImages(200, 503, PixelTypes.Rgba32, 61, 33)] + public void WorksWithDiscoBuffers( + TestImageProvider provider, + int workingBufferLimitInRows, + int bufferCapacityInRows) + where TPixel : unmanaged, IPixel + { + using Image expected = provider.GetImage(); + int width = expected.Width; + Size destSize = expected.Size() / 4; + expected.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + + // Replace configuration: + provider.Configuration = Configuration.CreateDefaultInstance(); + + // Note: when AllocatorCapacityInBytes < WorkingBufferSizeHintInBytes, + // ResizeProcessor is expected to use the minimum of the two values, when establishing the working buffer. + provider.LimitAllocatorBufferCapacity().InBytes(width * bufferCapacityInRows * SizeOfVector4); + provider.Configuration.WorkingBufferSizeHintInBytes = width * workingBufferLimitInRows * SizeOfVector4; + + using Image actual = provider.GetImage(); + actual.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + actual.DebugSave(provider, $"{workingBufferLimitInRows}-{bufferCapacityInRows}"); + + ImageComparer.Exact.VerifySimilarity(expected, actual); + } + [Theory] [WithTestPatternImages(100, 100, DefaultPixelType)] public void Resize_Compand(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -180,9 +204,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - string details = compand ? "Compand" : ""; + string details = compand ? "Compand" : string.Empty; provider.RunValidatingProcessorTest( x => x.Resize(x.GetCurrentSize() / 2, compand), @@ -194,7 +218,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -208,7 +232,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)] public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); } @@ -216,11 +240,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image0 = provider.GetImage()) { - var mmg = TestMemoryManager.CreateAsCopyOf(image0.GetPixelSpan()); + Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { @@ -238,7 +263,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void Resize_WorksWithAllParallelismLevels( TestImageProvider provider, int maxDegreeOfParallelism) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; @@ -280,7 +305,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms float? ratio, int? specificDestWidth, int? specificDestHeight) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = TestUtils.GetResampler(samplerName); @@ -300,31 +325,31 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms provider.RunValidatingProcessorTest( ctx => + { + SizeF newSize; + string destSizeInfo; + if (ratio.HasValue) { - SizeF newSize; - string destSizeInfo; - if (ratio.HasValue) - { - newSize = ctx.GetCurrentSize() * ratio.Value; - destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); - } - else + newSize = ctx.GetCurrentSize() * ratio.Value; + destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) { - if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) - { - throw new InvalidOperationException( - "invalid dimensional input for Resize_WorksWithAllResamplers!"); - } - - newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); - destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + throw new InvalidOperationException( + "invalid dimensional input for Resize_WorksWithAllResamplers!"); } - FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; + newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); + destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + } - ctx.Resize((Size)newSize, sampler, false); - return testOutputDetails; - }, + FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; + + ctx.Resize((Size)newSize, sampler, false); + return testOutputDetails; + }, comparer, appendPixelTypeToFileName: false); } @@ -332,7 +357,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeFromSourceRectangle(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -360,7 +385,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeHeightAndKeepAspect(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -374,7 +399,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(10, 100, DefaultPixelType)] public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -387,7 +412,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWidthAndKeepAspect(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -401,7 +426,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(100, 10, DefaultPixelType)] public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -414,14 +439,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithBoxPadMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height + 200), Mode = ResizeMode.BoxPad - }; + { + Size = new Size(image.Width + 200, image.Height + 200), + Mode = ResizeMode.BoxPad + }; image.Mutate(x => x.Resize(options)); @@ -433,7 +459,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropHeightMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -449,7 +475,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropWidthMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -462,10 +488,30 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithFile(TestImages.Jpeg.Issues.IncorrectResize1006, DefaultPixelType)] + public void CanResizeLargeImageWithCropMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Size = new Size(480, 600), + Mode = ResizeMode.Crop + }; + + image.Mutate(x => x.Resize(options)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } + [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithMaxMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -481,17 +527,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithMinMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions - { - Size = new Size( + { + Size = new Size( (int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), - Mode = ResizeMode.Min - }; + Mode = ResizeMode.Min + }; image.Mutate(x => x.Resize(options)); @@ -503,14 +549,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithPadMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height), Mode = ResizeMode.Pad - }; + { + Size = new Size(image.Width + 200, image.Height), + Mode = ResizeMode.Pad + }; image.Mutate(x => x.Resize(options)); @@ -522,14 +569,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithStretchMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var options = new ResizeOptions - { - Size = new Size(image.Width / 2, image.Height), Mode = ResizeMode.Stretch - }; + { + Size = new Size(image.Width / 2, image.Height), + Mode = ResizeMode.Stretch + }; image.Mutate(x => x.Resize(options)); @@ -537,5 +585,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms image.CompareToReferenceOutput(ValidatorComparer, provider); } } + + [Theory] + [WithFile(TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, DefaultPixelType)] + [WithFile(TestImages.Jpeg.Issues.ExifGetString750Transform, DefaultPixelType)] + [WithFile(TestImages.Jpeg.Issues.ExifResize1049, DefaultPixelType)] + public void CanResizeExifIssueImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Test images are large so skip on 32bit for now. + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + using (Image image = provider.GetImage()) + { + // Don't bother saving, we're testing the EXIF metadata updates. + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); + } + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs index 1e08836c13..04647c019a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(RotateFlipValues), 100, 50, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(RotateFlipValues), 50, 100, PixelTypes.Rgba32)] public void RotateFlip(TestImageProvider provider, RotateMode rotateType, FlipMode flipType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index 7801c71432..cf7c0c54ba 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithAngle(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs index ad77027f0f..0720bcfa2d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(nameof(SkewValues), 100, 50, CommonPixelTypes)] public void Skew_IsNotBoundToSinglePixelType(TestImageProvider provider, float x, float y) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); } @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = TestUtils.GetResampler(resamplerName); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index 70159e18ac..70b5be73e5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -1,15 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class AffineTransformBuilderTests : TransformBuilderTestBase { - protected override AffineTransformBuilder CreateBuilder(Rectangle rectangle) => new AffineTransformBuilder(); + protected override AffineTransformBuilder CreateBuilder() + => new AffineTransformBuilder(); protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees) => builder.AppendRotationDegrees(degrees); @@ -68,4 +68,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms return Vector2.Transform(sourcePoint, matrix); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs deleted file mode 100644 index ed6d3ef2bc..0000000000 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -using System; -using System.Numerics; -using System.Reflection; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; - -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Processing.Transforms -{ - public class AffineTransformTests - { - private readonly ITestOutputHelper Output; - - // 1 byte difference on one color component. - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0134F, 3); - - /// - /// angleDeg, sx, sy, tx, ty - /// - public static readonly TheoryData TransformValues - = new TheoryData - { - { 0, 1, 1, 0, 0 }, - { 50, 1, 1, 0, 0 }, - { 0, 1, 1, 20, 10 }, - { 50, 1, 1, 20, 10 }, - { 0, 1, 1, -20, -10 }, - { 50, 1, 1, -20, -10 }, - { 50, 1.5f, 1.5f, 0, 0 }, - { 50, 1.1F, 1.3F, 30, -20 }, - { 0, 2f, 1f, 0, 0 }, - { 0, 1f, 2f, 0, 0 }, - }; - - public static readonly TheoryData ResamplerNames = new TheoryData - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; - - public static readonly TheoryData Transform_DoesNotCreateEdgeArtifacts_ResamplerNames = - new TheoryData - { - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Lanczos8), - }; - - public AffineTransformTests(ITestOutputHelper output) => this.Output = output; - - /// - /// The output of an "all white" image should be "all white" or transparent, regardless of the transformation and the resampler. - /// - [Theory] - [WithSolidFilledImages(nameof(Transform_DoesNotCreateEdgeArtifacts_ResamplerNames), 5, 5, 255, 255, 255, 255, PixelTypes.Rgba32)] - public void Transform_DoesNotCreateEdgeArtifacts(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel - { - IResampler resampler = GetResampler(resamplerName); - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(30); - - image.Mutate(c => c.Transform(builder, resampler)); - image.DebugSave(provider, resamplerName); - - VerifyAllPixelsAreWhiteOrTransparent(image); - } - } - - [Theory] - [WithTestPatternImages(nameof(TransformValues), 100, 50, PixelTypes.Rgba32)] - public void Transform_RotateScaleTranslate( - TestImageProvider provider, - float angleDeg, - float sx, float sy, - float tx, float ty) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.DebugSave(provider, $"_original"); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(angleDeg) - .AppendScale(new SizeF(sx, sy)) - .AppendTranslation(new PointF(tx, ty)); - - this.PrintMatrix(builder.BuildMatrix(image.Size())); - - image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); - - FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})"; - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); - } - } - - [Theory] - [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] - public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(angleDeg) - .AppendScale(new SizeF(s, s)); - - image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); - - FormattableString testOutputDetails = $"R({angleDeg})_S({s})"; - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); - } - } - - public static readonly TheoryData Transform_IntoRectangle_Data = - new TheoryData - { - { 0, 0, 10, 10 }, - { 0, 0, 5, 10 }, - { 0, 0, 10, 5 }, - { 5, 0, 5, 10 }, - {-5,-5, 20, 20 } - }; - - /// - /// Testing transforms using custom source rectangles: - /// https://github.com/SixLabors/ImageSharp/pull/386#issuecomment-357104963 - /// - [Theory] - [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] - public void Transform_FromSourceRectangle1(TestImageProvider provider) - where TPixel : struct, IPixel - { - var rectangle = new Rectangle(48, 0, 48, 24); - - using (Image image = provider.GetImage()) - { - image.DebugSave(provider, $"_original"); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendScale(new SizeF(2, 1.5F)); - - image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } - - [Theory] - [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] - public void Transform_FromSourceRectangle2(TestImageProvider provider) - where TPixel : struct, IPixel - { - var rectangle = new Rectangle(0, 24, 48, 24); - - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendScale(new SizeF(1F, 2F)); - - image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } - - [Theory] - [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] - public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel - { - IResampler sampler = GetResampler(resamplerName); - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(50) - .AppendScale(new SizeF(.6F, .6F)); - - image.Mutate(i => i.Transform(builder, sampler)); - - image.DebugSave(provider, resamplerName); - image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); - } - } - - private static IResampler GetResampler(string name) - { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - - if (property is null) - { - throw new Exception($"No resampler named {name}"); - } - - return (IResampler)property.GetValue(null); - } - - private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) - where TPixel : struct, IPixel - { - Span data = image.Frames.RootFrame.GetPixelSpan(); - var white = new Rgb24(255, 255, 255); - foreach (TPixel pixel in data) - { - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - if (rgba.A == 0) - { - continue; - } - - Assert.Equal(white, rgba.Rgb); - } - } - - private void PrintMatrix(Matrix3x2 a) - { - string s = $"{a.M11:F10},{a.M12:F10},{a.M21:F10},{a.M22:F10},{a.M31:F10},{a.M32:F10}"; - this.Output.WriteLine(s); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs index 5350bd4a33..edf6a64403 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -5,7 +5,6 @@ using System; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index 7bb155f3a9..59d226ef5b 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using Xunit; @@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public class FlipTests : BaseImageOperationsExtensionTest { - [Theory] [InlineData(FlipMode.None)] [InlineData(FlipMode.Horizontal)] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 33da33c717..db1e76ae5d 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Pad(width, height); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index d82cd1689d..22388a0ac1 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -1,28 +1,33 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class ProjectiveTransformBuilderTests : TransformBuilderTestBase { - protected override ProjectiveTransformBuilder CreateBuilder(Rectangle rectangle) => new ProjectiveTransformBuilder(); + protected override ProjectiveTransformBuilder CreateBuilder() + => new ProjectiveTransformBuilder(); - protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees) => builder.AppendRotationDegrees(degrees); + protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees) + => builder.AppendRotationDegrees(degrees); - protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees, Vector2 origin) => builder.AppendRotationDegrees(degrees, origin); + protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees, Vector2 origin) + => builder.AppendRotationDegrees(degrees, origin); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) + => builder.AppendRotationRadians(radians); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => builder.AppendRotationRadians(radians, origin); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) + => builder.AppendRotationRadians(radians, origin); protected override void AppendScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY) - => builder.AppendSkewDegrees(degreesX, degreesY); + => builder.AppendSkewDegrees(degreesX, degreesY); protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) => builder.AppendSkewDegrees(degreesX, degreesY, origin); @@ -45,7 +50,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) => builder.PrependSkewRadians(radiansX, radiansY, origin); - protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.PrependTranslation(translate); + protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) + => builder.PrependTranslation(translate); protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => builder.PrependRotationRadians(radians, origin); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 3679180f49..c702aebe4b 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class ProjectiveTransformTests @@ -45,19 +45,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { TaperSide.Bottom, TaperCorner.Both }, { TaperSide.Bottom, TaperCorner.LeftOrTop }, { TaperSide.Bottom, TaperCorner.RightOrBottom }, - { TaperSide.Top, TaperCorner.Both }, { TaperSide.Top, TaperCorner.LeftOrTop }, { TaperSide.Top, TaperCorner.RightOrBottom }, - { TaperSide.Left, TaperCorner.Both }, { TaperSide.Left, TaperCorner.LeftOrTop }, { TaperSide.Left, TaperCorner.RightOrBottom }, - { TaperSide.Right, TaperCorner.Both }, { TaperSide.Right, TaperCorner.LeftOrTop }, { TaperSide.Right, TaperCorner.RightOrBottom }, - }; public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output; @@ -65,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = GetResampler(resamplerName); using (Image image = provider.GetImage()) @@ -81,9 +77,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } [Theory] - [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Rgba32.Red), PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -101,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] public void RawTransformMatchesDocumentedExample(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Printing some extra output to help investigating rounding errors: this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}"); @@ -126,17 +122,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] public void PerspectiveTransformMatchesCSS(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // https://jsfiddle.net/dFrHS/545/ // https://github.com/SixLabors/ImageSharp/issues/787 using (Image image = provider.GetImage()) { +#pragma warning disable SA1117 // Parameters should be on same line or separate lines var matrix = new Matrix4x4( 0.260987f, -0.434909f, 0, -0.0022184f, 0.373196f, 0.949882f, 0, -0.000312129f, 0, 0, 1, 0, 52, 165, 0, 1); +#pragma warning restore SA1117 // Parameters should be on same line or separate lines ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() .AppendMatrix(matrix); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index f268eda86c..e7b92b7b32 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms @@ -18,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); } [Fact] @@ -31,8 +30,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height, sampler); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } @@ -48,8 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height, sampler, compand); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); } @@ -74,8 +73,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(resizeOptions); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 71e3b71797..8c75cea7fd 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; using Xunit; @@ -26,11 +25,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [MemberData(nameof(ScaleTranslate_Data))] +#pragma warning disable SA1300 // Element should begin with upper-case letter public void _1Scale_2Translate(Vector2 scale, Vector2 translate, Vector2 source, Vector2 expectedDest) +#pragma warning restore SA1300 // Element should begin with upper-case letter { // These operations should be size-agnostic: var size = new Size(123, 321); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendScale(builder, new SizeF(scale)); this.AppendTranslation(builder, translate); @@ -50,11 +51,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [MemberData(nameof(TranslateScale_Data))] +#pragma warning disable SA1300 // Element should begin with upper-case letter public void _1Translate_2Scale(Vector2 translate, Vector2 scale, Vector2 source, Vector2 expectedDest) +#pragma warning restore SA1300 // Element should begin with upper-case letter { // Translate ans scale are size-agnostic: var size = new Size(456, 432); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendTranslation(builder, translate); this.AppendScale(builder, new SizeF(scale)); @@ -69,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void LocationOffsetIsPrepended(int locationX, int locationY) { var rectangle = new Rectangle(locationX, locationY, 10, 10); - TBuilder builder = this.CreateBuilder(rectangle); + TBuilder builder = this.CreateBuilder(); this.AppendScale(builder, new SizeF(2, 2)); @@ -91,12 +94,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendRotationDegrees(builder, degrees); // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); + Matrix3x2 matrix = TransformUtilities.CreateRotationMatrixDegrees(degrees, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -119,7 +122,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); var centerPoint = new Vector2(cx, cy); this.AppendRotationDegrees(builder, degrees, centerPoint); @@ -146,11 +149,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.AppendSkewDegrees(builder, degreesX, degreesY); - Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); + Matrix3x2 matrix = TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -173,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms float y) { var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); var centerPoint = new Vector2(cx, cy); this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint); @@ -191,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void AppendPrependOpposite() { var rectangle = new Rectangle(-1, -1, 3, 3); - TBuilder b1 = this.CreateBuilder(rectangle); - TBuilder b2 = this.CreateBuilder(rectangle); + TBuilder b1 = this.CreateBuilder(); + TBuilder b2 = this.CreateBuilder(); const float pi = (float)Math.PI; @@ -229,14 +232,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.ThrowsAny( () => { - TBuilder builder = this.CreateBuilder(size); + TBuilder builder = this.CreateBuilder(); this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero); }); } - protected TBuilder CreateBuilder(Size size) => this.CreateBuilder(new Rectangle(Point.Empty, size)); + [Fact] + public void ThrowsForInvalidMatrix() + { + Assert.ThrowsAny( + () => + { + TBuilder builder = this.CreateBuilder(); + this.AppendSkewDegrees(builder, 45, 45); + this.Execute(builder, new Rectangle(0, 0, 150, 150), Vector2.Zero); + }); + } - protected abstract TBuilder CreateBuilder(Rectangle rectangle); + protected abstract TBuilder CreateBuilder(); protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); @@ -272,4 +285,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs index 3ac9af960b..9f8034fa37 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -15,12 +15,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { int xy = 1; - using (var img = new Image(xy, xy)) + using (var img = new Image(xy, xy)) { var profile = new ExifProfile(); img.Metadata.ExifProfile = profile; - profile.SetValue(ExifTag.PixelXDimension, (uint)xy); - profile.SetValue(ExifTag.PixelYDimension, (uint)xy); + profile.SetValue(ExifTag.PixelXDimension, xy + ushort.MaxValue); + profile.SetValue(ExifTag.PixelYDimension, xy + ushort.MaxValue); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); @@ -32,4 +32,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index 95a47fd7cc..0b63d63775 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -13,6 +13,9 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; +// in this file, comments are used for disabling stuff for local execution +#pragma warning disable SA1515 + namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { public class JpegProfilingBenchmarks : MeasureFixture @@ -22,24 +25,28 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { } - public static readonly TheoryData DecodeJpegData = new TheoryData + public static readonly TheoryData DecodeJpegData = new TheoryData { - TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, - TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, + { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, + { TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, 40 }, + // { TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, 10 }, + // { TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, 5 }, + { TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, 5 } }; [Theory(Skip = ProfilingSetup.SkipProfilingTests)] [MemberData(nameof(DecodeJpegData))] - public void DecodeJpeg(string fileName) + public void DecodeJpeg(string fileName, int executionCount) { - this.DecodeJpegBenchmarkImpl(fileName, new JpegDecoder()); + var decoder = new JpegDecoder() + { + IgnoreMetadata = true + }; + this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); } - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder) + private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) { // do not run this on CI even by accident if (TestEnvironment.RunsOnCI) @@ -47,8 +54,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks return; } - const int ExecutionCount = 20; - if (!Vector.IsHardwareAccelerated) { throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); @@ -58,14 +63,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks byte[] bytes = File.ReadAllBytes(path); this.Measure( - ExecutionCount, + executionCount, () => { var img = Image.Load(bytes, decoder); img.Dispose(); }, - // ReSharper disable once ExplicitCallerInfoArgument +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // ReSharper disable once ExplicitCallerInfoArgument $"Decode {fileName}"); +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } // Benchmark, enable manually! @@ -101,8 +108,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks ms.Seek(0, SeekOrigin.Begin); } }, - // ReSharper disable once ExplicitCallerInfoArgument +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // ReSharper disable once ExplicitCallerInfoArgument $@"Encode {testFiles.Length} images"); +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } foreach (Image image in testImages) @@ -111,4 +120,4 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 95fe4e48f1..858607a02f 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -28,7 +28,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks using (var ms = new MemoryStream()) { - this.Measure(30, + this.Measure( + 30, () => { using (var image = Image.Load(configuration, imageBytes)) @@ -36,9 +37,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks image.Mutate(x => x.Resize(image.Size() / 4)); image.SaveAsJpeg(ms); } + ms.Seek(0, SeekOrigin.Begin); }); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs index f9a68d4e7c..34a1eaa30d 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // Uncomment to enable local profiling benchmarks. DO NOT PUSH TO MAIN! // #define PROFILING - namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { public static class ProfilingSetup @@ -15,4 +14,4 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks "Profiling benchmark, enable manually!"; #endif } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs index 8b93559381..ba5eb532b2 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs @@ -20,13 +20,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } public int ExecutionCount { get; set; } = 50; - + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] [InlineData(100, 100)] [InlineData(2000, 2000)] public void ResizeBicubic(int width, int height) { - this.Measure(this.ExecutionCount, + this.Measure( + this.ExecutionCount, () => { using (var image = new Image(this.configuration, width, height)) @@ -35,6 +36,5 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } }); } - } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 7750017095..7945741b01 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -22,15 +22,30 @@ namespace SixLabors.ImageSharp.Tests var octree = new OctreeQuantizer(); var wu = new WuQuantizer(); - Assert.NotNull(werner.Diffuser); - Assert.NotNull(webSafe.Diffuser); - Assert.NotNull(octree.Diffuser); - Assert.NotNull(wu.Diffuser); - - Assert.True(werner.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(webSafe.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(octree.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(wu.CreateFrameQuantizer(this.Configuration).Dither); + Assert.NotNull(werner.Options.Dither); + Assert.NotNull(webSafe.Options.Dither); + Assert.NotNull(octree.Options.Dither); + Assert.NotNull(wu.Options.Dither); + + using (IFrameQuantizer quantizer = werner.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IFrameQuantizer quantizer = webSafe.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IFrameQuantizer quantizer = octree.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IFrameQuantizer quantizer = wu.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } } [Theory] @@ -39,21 +54,28 @@ namespace SixLabors.ImageSharp.Tests public void OctreeQuantizerYieldsCorrectTransparentPixel( TestImageProvider provider, bool dither) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); + + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } - var quantizer = new OctreeQuantizer(dither); + var quantizer = new OctreeQuantizer(options); foreach (ImageFrame frame in image.Frames) { - IQuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + } } } } @@ -62,37 +84,44 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); - var quantizer = new WuQuantizer(dither); + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } + + var quantizer = new WuQuantizer(options); foreach (ImageFrame frame in image.Frames) { - IQuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + } } } } - private int GetTransparentIndex(IQuantizedFrame quantized) - where TPixel : struct, IPixel + private int GetTransparentIndex(IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { // Transparent pixels are much more likely to be found at the end of a palette int index = -1; - Rgba32 trans = default; ReadOnlySpan paletteSpan = quantized.Palette.Span; - for (int i = paletteSpan.Length - 1; i >= 0; i--) - { - paletteSpan[i].ToRgba32(ref trans); + Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - if (trans.Equals(default)) + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); + for (int i = colorSpan.Length - 1; i >= 0; i--) + { + if (colorSpan[i].Equals(default)) { index = i; } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index a1de7fd4b9..37b8cab60f 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -12,34 +15,40 @@ namespace SixLabors.ImageSharp.Tests.Quantization public void SinglePixelOpaque() { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - using (var image = new Image(config, 1, 1, Rgba32.Black)) - using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) - { - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + using var image = new Image(config, 1, 1, Color.Black); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(Rgba32.Black, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); - } + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(1, result.Height); + + Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelRowSpan(0)[0]); } [Fact] public void SinglePixelTransparent() { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - using (var image = new Image(config, 1, 1, default(Rgba32))) - using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) - { - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + using var image = new Image(config, 1, 1, default(Rgba32)); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); - } + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(1, result.Height); + + Assert.Equal(default, result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelRowSpan(0)[0]); } [Fact] @@ -60,63 +69,67 @@ namespace SixLabors.ImageSharp.Tests.Quantization [Fact] public void Palette256() { - using (var image = new Image(1, 256)) + using var image = new Image(1, 256); + + for (int i = 0; i < 256; i++) { - for (int i = 0; i < 256; i++) - { - byte r = (byte)((i % 4) * 85); - byte g = (byte)(((i / 4) % 4) * 85); - byte b = (byte)(((i / 16) % 4) * 85); - byte a = (byte)((i / 64) * 85); + byte r = (byte)((i % 4) * 85); + byte g = (byte)(((i / 4) % 4) * 85); + byte b = (byte)(((i / 16) % 4) * 85); + byte a = (byte)((i / 64) * 85); - image[0, i] = new Rgba32(r, g, b, a); - } + image[0, i] = new Rgba32(r, g, b, a); + } - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); - using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) - { - Assert.Equal(256, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - var actualImage = new Image(1, 256); + ImageFrame frame = image.Frames.RootFrame; - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; - for (int y = 0; y < actualImage.Height; y++) - { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); - int yy = y * actualImage.Width; + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); - for (int x = 0; x < actualImage.Width; x++) - { - int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; - } - } + Assert.Equal(256, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(256, result.Height); + + var actualImage = new Image(1, 256); + + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); - Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); + for (int x = 0; x < actualImage.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; } } + + for (int y = 0; y < image.Height; y++) + { + Assert.True(image.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); + } } [Theory] [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] public void LowVariance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // See https://github.com/SixLabors/ImageSharp/issues/866 using (Image image = provider.GetImage()) { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); - using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) - { - Assert.Equal(48, result.Palette.Length); - } + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + ImageFrame frame = image.Frames.RootFrame; + + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(48, result.Palette.Length); } } @@ -139,32 +152,35 @@ namespace SixLabors.ImageSharp.Tests.Quantization } Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + using (IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { Assert.Equal(4 * 8, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Assert.Equal(1, result.Width); + Assert.Equal(256, result.Height); ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; + int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); - int yy = y * actualImage.Width; + ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); for (int x = 0; x < actualImage.Width; x++) { - int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; } } } - Assert.True(expectedImage.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); + for (int y = 0; y < expectedImage.Height; y++) + { + Assert.True(expectedImage.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); + } } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/RunExtendedTests.cmd b/tests/ImageSharp.Tests/RunExtendedTests.cmd deleted file mode 100644 index c2f4b9f537..0000000000 --- a/tests/ImageSharp.Tests/RunExtendedTests.cmd +++ /dev/null @@ -1,9 +0,0 @@ -dotnet build -c Release -dotnet xunit -nobuild -c Release -f net462 -dotnet xunit -nobuild -c Release -f net462 -x86 -dotnet xunit -nobuild -c Release -f net47 -dotnet xunit -nobuild -c Release -f net47 -x86 -dotnet xunit -nobuild -c Release -f net471 -dotnet xunit -nobuild -c Release -f net471 -x86 -dotnet xunit -nobuild -c Release -f net472 -dotnet xunit -nobuild -c Release -f net472 -x86 diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs index 771e330389..a4d5e7c133 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataArray { - #region Byte - public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; public static readonly object[][] UInt8TestData = @@ -14,14 +12,9 @@ namespace SixLabors.ImageSharp.Tests new object[] { UInt8, UInt8 } }; - #endregion - - #region UInt16 - public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] UInt16_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, @@ -31,22 +24,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.UInt16_8, - IccTestDataPrimitives.UInt16_9 - ); + IccTestDataPrimitives.UInt16_9); public static readonly object[][] UInt16TestData = { new object[] { UInt16_Arr, UInt16_Val } }; - #endregion - - #region Int16 - public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] Int16_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Int16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int16_0, IccTestDataPrimitives.Int16_1, IccTestDataPrimitives.Int16_2, @@ -56,22 +43,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Int16_6, IccTestDataPrimitives.Int16_7, IccTestDataPrimitives.Int16_8, - IccTestDataPrimitives.Int16_9 - ); + IccTestDataPrimitives.Int16_9); public static readonly object[][] Int16TestData = { new object[] { Int16_Arr, Int16_Val } }; - #endregion - - #region UInt32 - public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] UInt32_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, @@ -81,22 +62,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.UInt32_7, IccTestDataPrimitives.UInt32_8, - IccTestDataPrimitives.UInt32_9 - ); + IccTestDataPrimitives.UInt32_9); public static readonly object[][] UInt32TestData = { new object[] { UInt32_Arr, UInt32_Val } }; - #endregion - - #region Int32 - public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] Int32_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Int32_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int32_0, IccTestDataPrimitives.Int32_1, IccTestDataPrimitives.Int32_2, @@ -106,22 +81,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Int32_6, IccTestDataPrimitives.Int32_7, IccTestDataPrimitives.Int32_8, - IccTestDataPrimitives.Int32_9 - ); + IccTestDataPrimitives.Int32_9); public static readonly object[][] Int32TestData = { new object[] { Int32_Arr, Int32_Val } }; - #endregion - - #region UInt64 - public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] UInt64_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt64_0, IccTestDataPrimitives.UInt64_1, IccTestDataPrimitives.UInt64_2, @@ -131,14 +100,11 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt64_6, IccTestDataPrimitives.UInt64_7, IccTestDataPrimitives.UInt64_8, - IccTestDataPrimitives.UInt64_9 - ); + IccTestDataPrimitives.UInt64_9); public static readonly object[][] UInt64TestData = { new object[] { UInt64_Arr, UInt64_Val } }; - - #endregion } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs index 334ee026db..837674e708 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -8,79 +8,64 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataCurves { - #region Response - +#pragma warning disable SA1118 // Parameter should not span multiple lines /// /// Channels: 3 /// - public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve - ( + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve( IccCurveMeasurementEncodings.StatusA, - new Vector3[] + new[] { IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccTestDataNonPrimitives.XyzNumber_ValVar3, + IccTestDataNonPrimitives.XyzNumber_ValVar3 }, new IccResponseNumber[][] { new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val1, IccTestDataNonPrimitives.ResponseNumber_Val2 }, new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val3, IccTestDataNonPrimitives.ResponseNumber_Val4 }, new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val5, IccTestDataNonPrimitives.ResponseNumber_Val6 }, - } - ); + }); +#pragma warning restore SA1118 // Parameter should not span multiple lines /// /// Channels: 3 /// - public static readonly byte[] Response_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Response_Grad = ArrayHelper.Concat( new byte[] { 0x53, 0x74, 0x61, 0x41 }, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UInt32_2, - IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, IccTestDataNonPrimitives.XyzNumber_Var3, - IccTestDataNonPrimitives.ResponseNumber_1, IccTestDataNonPrimitives.ResponseNumber_2, - IccTestDataNonPrimitives.ResponseNumber_3, IccTestDataNonPrimitives.ResponseNumber_4, - IccTestDataNonPrimitives.ResponseNumber_5, - IccTestDataNonPrimitives.ResponseNumber_6 - ); + IccTestDataNonPrimitives.ResponseNumber_6); public static readonly object[][] ResponseCurveTestData = { new object[] { Response_Grad, Response_ValGrad, 3 }, }; - #endregion - - #region Parametric - public static readonly IccParametricCurve Parametric_ValVar1 = new IccParametricCurve(1); public static readonly IccParametricCurve Parametric_ValVar2 = new IccParametricCurve(1, 2, 3); public static readonly IccParametricCurve Parametric_ValVar3 = new IccParametricCurve(1, 2, 3, 4); public static readonly IccParametricCurve Parametric_ValVar4 = new IccParametricCurve(1, 2, 3, 4, 5); public static readonly IccParametricCurve Parametric_ValVar5 = new IccParametricCurve(1, 2, 3, 4, 5, 6, 7); - public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x00, }, - IccTestDataPrimitives.Fix16_1 - ); + IccTestDataPrimitives.Fix16_1); - public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat( new byte[] { 0x00, 0x01, @@ -88,11 +73,9 @@ namespace SixLabors.ImageSharp.Tests }, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3 - ); + IccTestDataPrimitives.Fix16_3); - public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat( new byte[] { 0x00, 0x02, @@ -101,11 +84,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3, - IccTestDataPrimitives.Fix16_4 - ); + IccTestDataPrimitives.Fix16_4); - public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -115,11 +96,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3, IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_5 - ); + IccTestDataPrimitives.Fix16_5); - public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat( new byte[] { 0x00, 0x04, @@ -131,8 +110,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Fix16_7 - ); + IccTestDataPrimitives.Fix16_7); public static readonly object[][] ParametricCurveTestData = { @@ -143,16 +121,12 @@ namespace SixLabors.ImageSharp.Tests new object[] { Parametric_Var5, Parametric_ValVar5 }, }; - #endregion - - #region Formula Segment - + // Formula Segment public static readonly IccFormulaCurveElement Formula_ValVar1 = new IccFormulaCurveElement(IccFormulaCurveType.Type1, 1, 2, 3, 4, 0, 0); public static readonly IccFormulaCurveElement Formula_ValVar2 = new IccFormulaCurveElement(IccFormulaCurveType.Type2, 1, 2, 3, 4, 5, 0); public static readonly IccFormulaCurveElement Formula_ValVar3 = new IccFormulaCurveElement(IccFormulaCurveType.Type3, 0, 2, 3, 4, 5, 6); - public static readonly byte[] Formula_Var1 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, @@ -161,11 +135,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4 - ); + IccTestDataPrimitives.Single_4); - public static readonly byte[] Formula_Var2 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat( new byte[] { 0x00, 0x01, @@ -175,11 +147,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_5 - ); + IccTestDataPrimitives.Single_5); - public static readonly byte[] Formula_Var3 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat( new byte[] { 0x00, 0x02, @@ -189,8 +159,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, - IccTestDataPrimitives.Single_6 - ); + IccTestDataPrimitives.Single_6); public static readonly object[][] FormulaCurveSegmentTestData = { @@ -199,17 +168,12 @@ namespace SixLabors.ImageSharp.Tests new object[] { Formula_Var3, Formula_ValVar3 }, }; - #endregion - - #region Sampled Segment - + // Sampled Segment public static readonly IccSampledCurveElement Sampled_ValGrad1 = new IccSampledCurveElement(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); public static readonly IccSampledCurveElement Sampled_ValGrad2 = new IccSampledCurveElement(new float[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }); - public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat - ( + public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, @@ -218,13 +182,10 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_6, IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, - IccTestDataPrimitives.Single_9 - ); + IccTestDataPrimitives.Single_9); - public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat - ( + public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_9, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_7, @@ -233,8 +194,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_1 - ); + IccTestDataPrimitives.Single_1); public static readonly object[][] SampledCurveSegmentTestData = { @@ -242,65 +202,51 @@ namespace SixLabors.ImageSharp.Tests new object[] { Sampled_Grad2, Sampled_ValGrad2 }, }; - #endregion - - #region Segment - public static readonly IccCurveSegment Segment_ValFormula1 = Formula_ValVar1; public static readonly IccCurveSegment Segment_ValFormula2 = Formula_ValVar2; public static readonly IccCurveSegment Segment_ValFormula3 = Formula_ValVar3; public static readonly IccCurveSegment Segment_ValSampled1 = Sampled_ValGrad1; public static readonly IccCurveSegment Segment_ValSampled2 = Sampled_ValGrad2; - public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var1 - ); + Formula_Var1); - public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var2 - ); + Formula_Var2); - public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var3 - ); + Formula_Var3); - public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat( new byte[] { 0x73, 0x61, 0x6D, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Sampled_Grad1 - ); + Sampled_Grad1); - public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat( new byte[] { 0x73, 0x61, 0x6D, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Sampled_Grad2 - ); + Sampled_Grad2); public static readonly object[][] CurveSegmentTestData = { @@ -311,28 +257,19 @@ namespace SixLabors.ImageSharp.Tests new object[] { Segment_Sampled2, Segment_ValSampled2 }, }; - #endregion - - #region One Dimensional - - public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve - ( + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve( new float[] { 0, 1 }, - new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 } - ); - public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve - ( + new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 }); + + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve( new float[] { 0, 1 }, - new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 } - ); - public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve - ( + new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 }); + + public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve( new float[] { 0, 1 }, - new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 } - ); + new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 }); - public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -342,11 +279,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Formula1, Segment_Formula2, - Segment_Formula3 - ); + Segment_Formula3); - public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -356,11 +291,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Formula3, Segment_Formula2, - Segment_Formula1 - ); + Segment_Formula1); - public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -370,8 +303,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Sampled1, Segment_Sampled2, - Segment_Sampled1 - ); + Segment_Sampled1); public static readonly object[][] OneDimensionalCurveTestData = { @@ -379,7 +311,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs index 5ef2156c71..31f368cecc 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -7,22 +7,28 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataLut { - #region LUT8 - public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); public static readonly byte[] LUT8_Grad = CreateLUT8(); private static IccLut CreateLUT8Val() { float[] result = new float[256]; - for (int i = 0; i < 256; i++) { result[i] = i / 255f; } + for (int i = 0; i < 256; i++) + { + result[i] = i / 255f; + } + return new IccLut(result); } private static byte[] CreateLUT8() { byte[] result = new byte[256]; - for (int i = 0; i < 256; i++) { result[i] = (byte)i; } + for (int i = 0; i < 256; i++) + { + result[i] = (byte)i; + } + return result; } @@ -31,10 +37,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { LUT8_Grad, LUT8_ValGrad }, }; - #endregion - - #region LUT16 - public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] { 1f / ushort.MaxValue, @@ -50,8 +52,7 @@ namespace SixLabors.ImageSharp.Tests 1f }); - public static readonly byte[] LUT16_Grad = ArrayHelper.Concat - ( + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_3, @@ -62,20 +63,14 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.UInt16_32768, - IccTestDataPrimitives.UInt16_Max - ); + IccTestDataPrimitives.UInt16_Max); public static readonly object[][] Lut16TestData = { new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, }; - #endregion - - #region CLUT8 - - public static readonly IccClut CLUT8_ValGrad = new IccClut - ( + public static readonly IccClut CLUT8_ValGrad = new IccClut( new float[][] { new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, @@ -90,8 +85,8 @@ namespace SixLabors.ImageSharp.Tests new float[] { 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue }, new float[] { 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue }, }, - new byte[] { 3, 3 }, IccClutDataType.UInt8 - ); + new byte[] { 3, 3 }, + IccClutDataType.UInt8); /// /// Input Channel Count: 2 @@ -118,12 +113,7 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUT16 - - public static readonly IccClut CLUT16_ValGrad = new IccClut - ( + public static readonly IccClut CLUT16_ValGrad = new IccClut( new float[][] { new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, @@ -138,8 +128,8 @@ namespace SixLabors.ImageSharp.Tests new float[] { 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue }, new float[] { 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue }, }, - new byte[] { 3, 3 }, IccClutDataType.UInt16 - ); + new byte[] { 3, 3 }, + IccClutDataType.UInt16); /// /// Input Channel Count: 2 @@ -166,12 +156,7 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUTf32 - - public static readonly IccClut CLUTf32_ValGrad = new IccClut - ( + public static readonly IccClut CLUTf32_ValGrad = new IccClut( new float[][] { new float[] { 1f, 2f, 3f }, @@ -186,61 +171,65 @@ namespace SixLabors.ImageSharp.Tests new float[] { 4f, 5f, 6f }, new float[] { 7f, 8f, 9f }, }, - new byte[] { 3, 3 }, IccClutDataType.Float - ); + new byte[] { 3, 3 }, + IccClutDataType.Float); /// /// Input Channel Count: 2 /// Output Channel Count: 3 /// Grid-point Count: { 3, 3 } /// - public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat - ( - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, - - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, - - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9 - ); + public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9); public static readonly object[][] ClutF32TestData = { new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUT - public static readonly IccClut CLUT_Val8 = CLUT8_ValGrad; public static readonly IccClut CLUT_Val16 = CLUT16_ValGrad; public static readonly IccClut CLUT_Valf32 = CLUTf32_ValGrad; - public static readonly byte[] CLUT_8 = ArrayHelper.Concat - ( + public static readonly byte[] CLUT_8 = ArrayHelper.Concat( new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[4] { 0x01, 0x00, 0x00, 0x00 }, - CLUT8_Grad - ); + CLUT8_Grad); - public static readonly byte[] CLUT_16 = ArrayHelper.Concat - ( + public static readonly byte[] CLUT_16 = ArrayHelper.Concat( new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[4] { 0x02, 0x00, 0x00, 0x00 }, - CLUT16_Grad - ); + CLUT16_Grad); - public static readonly byte[] CLUT_f32 = ArrayHelper.Concat - ( + public static readonly byte[] CLUT_f32 = ArrayHelper.Concat( new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, - CLUTf32_Grad - ); + CLUTf32_Grad); public static readonly object[][] ClutTestData = { @@ -248,7 +237,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs index 799794ca4f..3bc787b344 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -1,16 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; namespace SixLabors.ImageSharp.Tests { - using SixLabors.ImageSharp.Primitives; + using SixLabors.ImageSharp; internal static class IccTestDataMatrix { - #region 2D - /// /// 3x3 Matrix /// @@ -20,6 +18,7 @@ namespace SixLabors.ImageSharp.Tests { 4, 5, 6 }, { 7, 8, 9 }, }; + /// /// 3x3 Matrix /// @@ -53,56 +52,44 @@ namespace SixLabors.ImageSharp.Tests /// /// 3x3 Matrix /// - public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_7, - IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_8, - IccTestDataPrimitives.Fix16_3, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Fix16_9 - ); + IccTestDataPrimitives.Fix16_9); /// /// 3x3 Matrix /// - public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, - IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_0, - IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, - IccTestDataPrimitives.Fix16_1 - ); + IccTestDataPrimitives.Fix16_1); /// /// 3x3 Matrix /// - public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_7, - IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_8, - IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_9 - ); + IccTestDataPrimitives.Single_9); public static readonly object[][] Matrix2D_FloatArrayTestData = { @@ -125,14 +112,11 @@ namespace SixLabors.ImageSharp.Tests new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, }; - #endregion - - #region 1D - /// /// 3x1 Matrix /// public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; + /// /// 3x1 Matrix /// @@ -141,22 +125,18 @@ namespace SixLabors.ImageSharp.Tests /// /// 3x1 Matrix /// - public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_7 - ); + IccTestDataPrimitives.Fix16_7); /// /// 3x1 Matrix /// - public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_7 - ); + IccTestDataPrimitives.Single_7); public static readonly object[][] Matrix1D_ArrayTestData = { @@ -169,7 +149,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs index 586e846801..d7f9dd877a 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -1,14 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { - internal static class IccTestDataMultiProcessElement + internal static class IccTestDataMultiProcessElements { - #region CurveSet - /// /// Input Channel Count: 3 /// Output Channel Count: 3 @@ -19,60 +17,48 @@ namespace SixLabors.ImageSharp.Tests IccTestDataCurves.OneDimensional_ValFormula2, IccTestDataCurves.OneDimensional_ValFormula1 }); + /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat - ( + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat( IccTestDataCurves.OneDimensional_Formula1, IccTestDataCurves.OneDimensional_Formula2, - IccTestDataCurves.OneDimensional_Formula1 - ); + IccTestDataCurves.OneDimensional_Formula1); public static readonly object[][] CurveSetTestData = { new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, }; - #endregion - - #region Matrix - /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement - ( + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement( IccTestDataMatrix.Single_2DArray_ValGrad, - IccTestDataMatrix.Single_1DArray_ValGrad - ); + IccTestDataMatrix.Single_1DArray_ValGrad); + /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat - ( + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat( IccTestDataMatrix.Single_2D_Grad, - IccTestDataMatrix.Single_1D_Grad - ); + IccTestDataMatrix.Single_1D_Grad); public static readonly object[][] MatrixTestData = { new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, }; - - #endregion - - #region CLUT - /// /// Input Channel Count: 2 /// Output Channel Count: 3 /// public static readonly IccClutProcessElement CLUTPE_ValGrad = new IccClutProcessElement(IccTestDataLut.CLUT_Valf32); + /// /// Input Channel Count: 2 /// Output Channel Count: 3 @@ -84,48 +70,38 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, }; - #endregion - - #region MultiProcessElement - public static readonly IccMultiProcessElement MPE_ValMatrix = MatrixPE_ValGrad; public static readonly IccMultiProcessElement MPE_ValCLUT = CLUTPE_ValGrad; public static readonly IccMultiProcessElement MPE_ValCurve = CurvePE_ValGrad; public static readonly IccMultiProcessElement MPE_ValbACS = new IccBAcsProcessElement(3, 3); public static readonly IccMultiProcessElement MPE_ValeACS = new IccEAcsProcessElement(3, 3); - public static readonly byte[] MPE_Matrix = ArrayHelper.Concat - ( + public static readonly byte[] MPE_Matrix = ArrayHelper.Concat( new byte[] { 0x6D, 0x61, 0x74, 0x66, 0x00, 0x03, 0x00, 0x03, }, - MatrixPE_Grad - ); + MatrixPE_Grad); - public static readonly byte[] MPE_CLUT = ArrayHelper.Concat - ( + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat( new byte[] { 0x63, 0x6C, 0x75, 0x74, 0x00, 0x02, 0x00, 0x03, }, - CLUTPE_Grad - ); + CLUTPE_Grad); - public static readonly byte[] MPE_Curve = ArrayHelper.Concat - ( + public static readonly byte[] MPE_Curve = ArrayHelper.Concat( new byte[] { 0x6D, 0x66, 0x6C, 0x74, 0x00, 0x03, 0x00, 0x03, }, - CurvePE_Grad - ); + CurvePE_Grad); public static readonly byte[] MPE_bACS = { @@ -151,7 +127,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { MPE_bACS, MPE_ValbACS }, new object[] { MPE_eACS, MPE_ValeACS }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs index 44af423479..91f81cb433 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -10,8 +10,6 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataNonPrimitives { - #region DateTime - public static readonly DateTime DateTime_ValMin = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static readonly DateTime DateTime_ValMax = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc); public static readonly DateTime DateTime_ValRand1 = new DateTime(1990, 11, 26, 3, 19, 47, DateTimeKind.Utc); @@ -63,10 +61,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { DateTime_Rand1, DateTime_ValRand1 }, }; - #endregion - - #region VersionNumber - public static readonly IccVersion VersionNumber_ValMin = new IccVersion(0, 0, 0); public static readonly IccVersion VersionNumber_Val211 = new IccVersion(2, 1, 1); public static readonly IccVersion VersionNumber_Val430 = new IccVersion(4, 3, 0); @@ -85,10 +79,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { VersionNumber_Max, VersionNumber_ValMax }, }; - #endregion - - #region XyzNumber - public static readonly Vector3 XyzNumber_ValMin = new Vector3(IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin); public static readonly Vector3 XyzNumber_Val0 = new Vector3(0, 0, 0); public static readonly Vector3 XyzNumber_Val1 = new Vector3(1, 1, 1); @@ -113,10 +103,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { XyzNumber_Max, XyzNumber_ValMax }, }; - #endregion - - #region ProfileId - public static readonly IccProfileId ProfileId_ValMin = new IccProfileId(0, 0, 0, 0); public static readonly IccProfileId ProfileId_ValRand = new IccProfileId(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2, IccTestDataPrimitives.UInt32_ValRand3, IccTestDataPrimitives.UInt32_ValRand4); public static readonly IccProfileId ProfileId_ValMax = new IccProfileId(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); @@ -132,10 +118,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { ProfileId_Max, ProfileId_ValMax }, }; - #endregion - - #region PositionNumber - public static readonly IccPositionNumber PositionNumber_ValMin = new IccPositionNumber(0, 0); public static readonly IccPositionNumber PositionNumber_ValRand = new IccPositionNumber(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2); public static readonly IccPositionNumber PositionNumber_ValMax = new IccPositionNumber(uint.MaxValue, uint.MaxValue); @@ -151,10 +133,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { PositionNumber_Max, PositionNumber_ValMax }, }; - #endregion - - #region ResponseNumber - public static readonly IccResponseNumber ResponseNumber_ValMin = new IccResponseNumber(0, IccTestDataPrimitives.Fix16_ValMin); public static readonly IccResponseNumber ResponseNumber_Val1 = new IccResponseNumber(1, 1); public static readonly IccResponseNumber ResponseNumber_Val2 = new IccResponseNumber(2, 2); @@ -187,43 +165,48 @@ namespace SixLabors.ImageSharp.Tests new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, }; - #endregion - - #region NamedColor - - public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor - ( + public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor( ArrayHelper.Fill('A', 31), new ushort[] { 0, 0, 0 }, - new ushort[] { 0, 0, 0 } - ); - public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor - ( + new ushort[] { 0, 0, 0 }); + + public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor( ArrayHelper.Fill('5', 31), new ushort[] { 10794, 10794, 10794 }, - new ushort[] { 17219, 17219, 17219, 17219, 17219 } - ); - public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor - ( + new ushort[] { 17219, 17219, 17219, 17219, 17219 }); + + public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor( ArrayHelper.Fill('4', 31), new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }, - new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue } - ); + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }); public static readonly byte[] NamedColor_Min = CreateNamedColor(3, 0x41, 0x00, 0x00); public static readonly byte[] NamedColor_Rand = CreateNamedColor(5, 0x35, 42, 67); public static readonly byte[] NamedColor_Max = CreateNamedColor(4, 0x34, 0xFF, 0xFF); - private static byte[] CreateNamedColor(int devCoordCount, byte name, byte PCS, byte device) + private static byte[] CreateNamedColor(int devCoordCount, byte name, byte pCS, byte device) { - byte[] data = new byte[32 + 6 + devCoordCount * 2]; + byte[] data = new byte[32 + 6 + (devCoordCount * 2)]; for (int i = 0; i < data.Length; i++) { - if (i < 31) { data[i] = name; } // Name - else if (i == 31) { data[i] = 0x00; } // Name null terminator - else if (i < 32 + 6) { data[i] = PCS; } // PCS Coordinates - else { data[i] = device; } // Device Coordinates + if (i < 31) + { + data[i] = name; // Name + } + else if (i is 31) + { + data[i] = 0x00; // Name null terminator + } + else if (i < 32 + 6) + { + data[i] = pCS; // PCS Coordinates + } + else + { + data[i] = device; // Device Coordinates + } } + return data; } @@ -234,10 +217,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, }; - #endregion - - #region ProfileDescription - private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); @@ -251,91 +230,76 @@ namespace SixLabors.ImageSharp.Tests }; private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); - private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat - ( + private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'d', (byte)'e', (byte)'A', (byte)'T' }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); - - public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry - ( - IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), - 1701729619, 2 - ); - public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat - ( + IccTestDataPrimitives.Unicode_Rand3); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand, + IccTestDataPrimitives.Unicode_ValRand1, + ArrayHelper.Fill('A', 66), + 1701729619, + 2); + + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 IccTestDataPrimitives.Ascii_Rand, new byte[] { 0x00 }, // Null terminator - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x07 }, // 7 IccTestDataPrimitives.Unicode_Rand2, new byte[] { 0x00, 0x00 }, // Null terminator - new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 } // Null terminator - ); + new byte[] { 0x00 }); // Null terminator - public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription - ( - 1, 2, + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription( + 1, + 2, IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, IccProfileTag.ProfileDescription, MultiLocalizedUnicode_Val.Texts, - MultiLocalizedUnicode_Val.Texts - ); + MultiLocalizedUnicode_Val.Texts); - public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription - ( - 1, 2, + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription( + 1, + 2, IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, IccProfileTag.ProfileDescription, new IccLocalizedString[] { LocalizedString_Rand1 }, - new IccLocalizedString[] { LocalizedString_Rand1 } - ); + new IccLocalizedString[] { LocalizedString_Rand1 }); - public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat - ( + public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, MultiLocalizedUnicode_Arr, new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, - MultiLocalizedUnicode_Arr - ); + MultiLocalizedUnicode_Arr); - public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x64, 0x65, 0x73, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, TextDescription_Arr1, new byte[] { 0x64, 0x65, 0x73, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, - TextDescription_Arr1 - ); + TextDescription_Arr1); public static readonly object[][] ProfileDescriptionReadTestData = { @@ -348,30 +312,22 @@ namespace SixLabors.ImageSharp.Tests new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, }; - #endregion - - #region ColorantTableEntry - public static readonly IccColorantTableEntry ColorantTableEntry_ValRand1 = new IccColorantTableEntry(ArrayHelper.Fill('A', 31), 1, 2, 3); public static readonly IccColorantTableEntry ColorantTableEntry_ValRand2 = new IccColorantTableEntry(ArrayHelper.Fill('4', 31), 4, 5, 6); - public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat - ( + public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat( ArrayHelper.Fill((byte)0x41, 31), new byte[1], // null terminator IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); - public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat( ArrayHelper.Fill((byte)0x34, 31), new byte[1], // null terminator IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_6 - ); + IccTestDataPrimitives.UInt16_6); public static readonly object[][] ColorantTableEntryTestData = { @@ -379,33 +335,23 @@ namespace SixLabors.ImageSharp.Tests new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, }; - #endregion - - #region ScreeningChannel - public static readonly IccScreeningChannel ScreeningChannel_ValRand1 = new IccScreeningChannel(4, 6, IccScreeningSpotType.Cross); public static readonly IccScreeningChannel ScreeningChannel_ValRand2 = new IccScreeningChannel(8, 5, IccScreeningSpotType.Diamond); - public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat - ( + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Int32_7 - ); + IccTestDataPrimitives.Int32_7); - public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_5, - IccTestDataPrimitives.Int32_3 - ); + IccTestDataPrimitives.Int32_3); public static readonly object[][] ScreeningChannelTestData = { new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs index b24e3f24a4..c7b856a601 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataPrimitives { - #region UInt16 - public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; @@ -20,10 +18,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; - #endregion - - #region Int16 - public static readonly byte[] Int16_Min = { 0x80, 0x00 }; public static readonly byte[] Int16_0 = { 0x00, 0x00 }; public static readonly byte[] Int16_1 = { 0x00, 0x01 }; @@ -37,10 +31,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Int16_9 = { 0x00, 0x09 }; public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; - #endregion - - #region UInt32 - public static readonly byte[] UInt32_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] UInt32_1 = { 0x00, 0x00, 0x00, 0x01 }; public static readonly byte[] UInt32_2 = { 0x00, 0x00, 0x00, 0x02 }; @@ -53,7 +43,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt32_9 = { 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] UInt32_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; - public static readonly uint UInt32_ValRand1 = 1749014123; public static readonly uint UInt32_ValRand2 = 3870560989; public static readonly uint UInt32_ValRand3 = 1050090334; @@ -64,10 +53,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; - #endregion - - #region Int32 - public static readonly byte[] Int32_Min = { 0x80, 0x00, 0x00, 0x00 }; public static readonly byte[] Int32_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Int32_1 = { 0x00, 0x00, 0x00, 0x01 }; @@ -81,10 +66,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; - #endregion - - #region UInt64 - public static readonly byte[] UInt64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] UInt64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; public static readonly byte[] UInt64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; @@ -97,10 +78,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] UInt64_Max = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - #endregion - - #region Int64 - public static readonly byte[] Int64_Min = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Int64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Int64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; @@ -114,10 +91,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Int64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] Int64_Max = { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - #endregion - - #region Single - public static readonly byte[] Single_Min = { 0xFF, 0x7F, 0xFF, 0xFF }; public static readonly byte[] Single_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Single_1 = { 0x3F, 0x80, 0x00, 0x00 }; @@ -131,21 +104,13 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; - #endregion - - #region Double - public static readonly byte[] Double_Min = { 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; public static readonly byte[] Double_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Double_1 = { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Double_Max = { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - #endregion - - #region Fix16 - public const float Fix16_ValMin = short.MinValue; - public const float Fix16_ValMax = short.MaxValue + 65535f / 65536f; + public const float Fix16_ValMax = short.MaxValue + (65535f / 65536f); public static readonly byte[] Fix16_Min = { 0x80, 0x00, 0x00, 0x00 }; public static readonly byte[] Fix16_0 = { 0x00, 0x00, 0x00, 0x00 }; @@ -168,12 +133,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { Fix16_Max, Fix16_ValMax }, }; - #endregion - - #region UFix16 - public const float UFix16_ValMin = 0; - public const float UFix16_ValMax = ushort.MaxValue + 65535f / 65536f; + public const float UFix16_ValMax = ushort.MaxValue + (65535f / 65536f); public static readonly byte[] UFix16_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] UFix16_1 = { 0x00, 0x01, 0x00, 0x00 }; @@ -194,12 +155,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { UFix16_Max, UFix16_ValMax }, }; - #endregion - - #region U1Fix15 - public const float U1Fix15_ValMin = 0; - public const float U1Fix15_ValMax = 1f + 32767f / 32768f; + public const float U1Fix15_ValMax = 1f + (32767f / 32768f); public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; @@ -212,12 +169,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { U1Fix15_Max, U1Fix15_ValMax }, }; - #endregion - - #region UFix8 - public const float UFix8_ValMin = 0; - public const float UFix8_ValMax = byte.MaxValue + 255f / 256f; + public const float UFix8_ValMax = byte.MaxValue + (255f / 256f); public static readonly byte[] UFix8_0 = { 0x00, 0x00 }; public static readonly byte[] UFix8_1 = { 0x01, 0x00 }; @@ -238,10 +191,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { UFix8_Max, UFix8_ValMax }, }; - #endregion - - #region ASCII String - public const string Ascii_ValRand = "aBcdEf1234"; public const string Ascii_ValRand1 = "Ecf3a"; public const string Ascii_ValRand2 = "2Bd4c"; @@ -283,10 +232,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, }; - #endregion - - #region Unicode String - public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; public const string Unicode_ValRand2 = ".6Abäñ"; public const string Unicode_ValRand3 = "$€β𐐷𤭢"; @@ -324,7 +269,5 @@ namespace SixLabors.ImageSharp.Tests 0xD8, 0x01, 0xDC, 0x37, // 𐐷 0xD8, 0x52, 0xDF, 0x62, // 𤭢 }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index 49ff190467..671edcfae0 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -78,41 +78,46 @@ namespace SixLabors.ImageSharp.Tests 0x64, 0x63, 0x62, 0x61, // CreatorSignature }, profileId, +#pragma warning disable SA1118 // Parameter should not span multiple lines new byte[] - { + { // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Nr of tag table entries - (byte)(nrOfEntries >> 24), (byte)(nrOfEntries >> 16), (byte)(nrOfEntries >> 8), (byte)nrOfEntries + (byte)(nrOfEntries >> 24), + (byte)(nrOfEntries >> 16), + (byte)(nrOfEntries >> 8), + (byte)nrOfEntries }); +#pragma warning restore SA1118 // Parameter should not span multiple lines } - public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), + public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat( + CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), +#pragma warning disable SA1118 // Parameter should not span multiple lines new byte[] { 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) 0x00, 0x00, 0x00, 0x9C, // tag offset (156) 0x00, 0x00, 0x00, 0x0C, // tag size (12) - 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) 0x00, 0x00, 0x00, 0x9C, // tag offset (156) 0x00, 0x00, 0x00, 0x0C, // tag size (12) }, +#pragma warning restore SA1118 // Parameter should not span multiple lines IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, - IccTestDataTagDataEntry.Unknown_Arr - ); + IccTestDataTagDataEntry.Unknown_Arr); - public static readonly IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, - Profile_Random_Id_Value, - "acsp"), - new IccTagDataEntry[] - { - IccTestDataTagDataEntry.Unknown_Val, - IccTestDataTagDataEntry.Unknown_Val - }); + public static readonly IccProfile Profile_Random_Val = new IccProfile( + CreateHeaderRandomValue( + 168, + Profile_Random_Id_Value, + "acsp"), + new IccTagDataEntry[] { IccTestDataTagDataEntry.Unknown_Val, IccTestDataTagDataEntry.Unknown_Val }); public static readonly byte[] Header_CorruptDataColorSpace_Array = { @@ -132,8 +137,10 @@ namespace SixLabors.ImageSharp.Tests 0x00, 0x00, 0x00, 0x03, // RenderingIntent 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -159,8 +166,10 @@ namespace SixLabors.ImageSharp.Tests 0x00, 0x00, 0x00, 0x03, // RenderingIntent 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -186,8 +195,10 @@ namespace SixLabors.ImageSharp.Tests 0x33, 0x41, 0x30, 0x6B, // RenderingIntent 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -221,4 +232,4 @@ namespace SixLabors.ImageSharp.Tests new object[] { Header_Random_Array, true }, }; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs index b5da224435..bfe7d94b11 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Globalization; @@ -9,8 +9,6 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataTagDataEntry { - #region TagDataEntry Header - public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; public static readonly byte[] TagDataEntryHeader_UnknownArr = { @@ -39,10 +37,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, }; - #endregion - - #region UnknownTagDataEntry - public static readonly IccUnknownTagDataEntry Unknown_Val = new IccUnknownTagDataEntry(new byte[] { 0x00, 0x01, 0x02, 0x03 }); public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; @@ -51,63 +45,45 @@ namespace SixLabors.ImageSharp.Tests new object[] { Unknown_Arr, Unknown_Val, 12u }, }; - #endregion - - #region ChromaticityTagDataEntry - public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new IccChromaticityTagDataEntry(IccColorantEncoding.ItuRBt709_2); - public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt16_1, - new byte[] { 0x00, 0x00, 0xA3, 0xD7 }, // 0.640 new byte[] { 0x00, 0x00, 0x54, 0x7B }, // 0.330 - new byte[] { 0x00, 0x00, 0x4C, 0xCD }, // 0.300 new byte[] { 0x00, 0x00, 0x99, 0x9A }, // 0.600 - new byte[] { 0x00, 0x00, 0x26, 0x66 }, // 0.150 - new byte[] { 0x00, 0x00, 0x0F, 0x5C } // 0.060 - ); + new byte[] { 0x00, 0x00, 0x0F, 0x5C }); // 0.060 - public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry - ( + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry( new double[][] { new double[] { 1, 2 }, new double[] { 3, 4 }, - } - ); - public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat - ( + }); + + public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_0, - IccTestDataPrimitives.UFix16_1, IccTestDataPrimitives.UFix16_2, - IccTestDataPrimitives.UFix16_3, - IccTestDataPrimitives.UFix16_4 - ); + IccTestDataPrimitives.UFix16_4); /// /// : channel count must be 3 for any enum other than /// - public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_1 - ); + IccTestDataPrimitives.UInt16_1); /// /// : invalid enum value /// - public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, - IccTestDataPrimitives.UInt16_9 - ); + IccTestDataPrimitives.UInt16_9); public static readonly object[][] ChromaticityTagDataEntryTestData = { @@ -115,10 +91,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, }; - #endregion - - #region ColorantOrderTagDataEntry - public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new IccColorantOrderTagDataEntry(new byte[] { 0x00, 0x01, 0x02 }); public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); @@ -127,52 +99,37 @@ namespace SixLabors.ImageSharp.Tests new object[] { ColorantOrder_Arr, ColorantOrder_Val }, }; - #endregion - - #region ColorantTableTagDataEntry - - public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry - ( + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry( new IccColorantTableEntry[] { IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 - } - ); - public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ColorantTableEntry_Rand1, - IccTestDataNonPrimitives.ColorantTableEntry_Rand2 - ); + IccTestDataNonPrimitives.ColorantTableEntry_Rand2); public static readonly object[][] ColorantTableTagDataEntryTestData = { new object[] { ColorantTable_Arr, ColorantTable_Val }, }; - #endregion - - #region CurveTagDataEntry - public static readonly IccCurveTagDataEntry Curve_Val_0 = new IccCurveTagDataEntry(); public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; public static readonly IccCurveTagDataEntry Curve_Val_1 = new IccCurveTagDataEntry(1f); - public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat - ( + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, - IccTestDataPrimitives.UFix8_1 - ); + IccTestDataPrimitives.UFix8_1); public static readonly IccCurveTagDataEntry Curve_Val_2 = new IccCurveTagDataEntry(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); - public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat - ( + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_3, IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); public static readonly object[][] CurveTagDataEntryTestData = { @@ -181,26 +138,20 @@ namespace SixLabors.ImageSharp.Tests new object[] { Curve_Arr_2, Curve_Val_2 }, }; - #endregion - - #region DataTagDataEntry - - public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry - ( + public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry( new byte[] { 0x01, 0x02, 0x03, 0x04 }, - false - ); + false); + public static readonly byte[] Data_ArrNoASCII = { 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04 }; - public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry - ( + public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry( new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, - true - ); + true); + public static readonly byte[] Data_ArrASCII = { 0x00, 0x00, 0x00, 0x01, @@ -213,10 +164,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Data_ArrASCII, Data_ValASCII, 17u }, }; - #endregion - - #region DateTimeTagDataEntry - public static readonly IccDateTimeTagDataEntry DateTime_Val = new IccDateTimeTagDataEntry(IccTestDataNonPrimitives.DateTime_ValRand1); public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; @@ -225,89 +172,60 @@ namespace SixLabors.ImageSharp.Tests new object[] { DateTime_Arr, DateTime_Val }, }; - #endregion - - #region Lut16TagDataEntry - - public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry - ( + public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry( new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }, IccTestDataLut.CLUT16_ValGrad, - new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad } - ); - public static readonly byte[] Lut16_Arr = ArrayHelper.Concat - ( + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }); + + public static readonly byte[] Lut16_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x03, 0x00 }, IccTestDataMatrix.Fix16_2D_Identity, new byte[] { 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length, 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length }, - IccTestDataLut.LUT16_Grad, IccTestDataLut.LUT16_Grad, - IccTestDataLut.CLUT16_Grad, - IccTestDataLut.LUT16_Grad, IccTestDataLut.LUT16_Grad, - IccTestDataLut.LUT16_Grad - ); + IccTestDataLut.LUT16_Grad); public static readonly object[][] Lut16TagDataEntryTestData = { new object[] { Lut16_Arr, Lut16_Val }, }; - #endregion - - #region Lut8TagDataEntry - - public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry - ( + public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry( new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }, IccTestDataLut.CLUT8_ValGrad, - new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad } - ); - public static readonly byte[] Lut8_Arr = ArrayHelper.Concat - ( + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }); + + public static readonly byte[] Lut8_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x03, 0x00 }, IccTestDataMatrix.Fix16_2D_Identity, - IccTestDataLut.LUT8_Grad, IccTestDataLut.LUT8_Grad, - IccTestDataLut.CLUT8_Grad, - IccTestDataLut.LUT8_Grad, IccTestDataLut.LUT8_Grad, - IccTestDataLut.LUT8_Grad - ); + IccTestDataLut.LUT8_Grad); public static readonly object[][] Lut8TagDataEntryTestData = { new object[] { Lut8_Arr, Lut8_Val }, }; - #endregion - - #region LutAToBTagDataEntry - - private static readonly byte[] CurveFull_0 = ArrayHelper.Concat - ( + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_0 - ); - private static readonly byte[] CurveFull_1 = ArrayHelper.Concat - ( + Curve_Arr_0); + + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_1 - ); - private static readonly byte[] CurveFull_2 = ArrayHelper.Concat - ( + Curve_Arr_1); + + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_2 - ); + Curve_Arr_2); - public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry - ( + public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry( new IccCurveTagDataEntry[] { Curve_Val_0, @@ -316,23 +234,13 @@ namespace SixLabors.ImageSharp.Tests }, IccTestDataMatrix.Single_2DArray_ValGrad, IccTestDataMatrix.Single_1DArray_ValGrad, - new IccCurveTagDataEntry[] - { - Curve_Val_1, - Curve_Val_2, - Curve_Val_0, - }, + new IccCurveTagDataEntry[] { Curve_Val_1, Curve_Val_2, Curve_Val_0 }, IccTestDataLut.CLUT_Val16, - new IccCurveTagDataEntry[] - { - Curve_Val_2, - Curve_Val_1, - } - ); - public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat - ( - new byte[] { 0x02, 0x03, 0x00, 0x00 }, + new IccCurveTagDataEntry[] { Curve_Val_2, Curve_Val_1 }); +#pragma warning disable SA1115 // Parameter should follow comma + public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 @@ -365,21 +273,17 @@ namespace SixLabors.ImageSharp.Tests CurveFull_2, // 18 bytes new byte[] { 0x00, 0x00 }, // Padding CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 } // Padding - ); + new byte[] { 0x00, 0x00 }); // Padding + +#pragma warning restore SA1115 // Parameter should follow comma public static readonly object[][] LutAToBTagDataEntryTestData = { new object[] { LutAToB_Arr, LutAToB_Val }, }; - #endregion - - #region LutBToATagDataEntry - - public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry - ( - new IccCurveTagDataEntry[] + public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry( + new[] { Curve_Val_0, Curve_Val_1, @@ -388,15 +292,10 @@ namespace SixLabors.ImageSharp.Tests null, null, IccTestDataLut.CLUT_Val16, - new IccCurveTagDataEntry[] - { - Curve_Val_2, - Curve_Val_1, - Curve_Val_0, - } - ); - public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat - ( + new[] { Curve_Val_2, Curve_Val_1, Curve_Val_0 }); + +#pragma warning disable SA1115 // Parameter should follow comma + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 @@ -406,8 +305,8 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 // B - CurveFull_0, //12 bytes - CurveFull_1, //14 bytes + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes new byte[] { 0x00, 0x00 }, // Padding // CLUT @@ -419,41 +318,34 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0x00, 0x00 }, // Padding CurveFull_1, // 14 bytes new byte[] { 0x00, 0x00 }, // Padding - CurveFull_0 // 12 bytes - ); + CurveFull_0); // 12 bytes + +#pragma warning restore SA1115 // Parameter should follow comma public static readonly object[][] LutBToATagDataEntryTestData = { new object[] { LutBToA_Arr, LutBToA_Val }, }; - #endregion - - #region MeasurementTagDataEntry + public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry( + IccStandardObserver.Cie1931Observer, + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccMeasurementGeometry.Degree0ToDOrDTo0, + 1f, + IccStandardIlluminant.D50); - public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry - ( - IccStandardObserver.Cie1931Observer, IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccMeasurementGeometry.Degree0ToDOrDTo0, 1f, IccStandardIlluminant.D50 - ); - public static readonly byte[] Measurement_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UFix16_1, - IccTestDataPrimitives.UInt32_1 - ); + IccTestDataPrimitives.UInt32_1); public static readonly object[][] MeasurementTagDataEntryTestData = { new object[] { Measurement_Arr, Measurement_Val }, }; - #endregion - - #region MultiLocalizedUnicodeTagDataEntry - private static readonly IccLocalizedString LocalizedString_Rand_enUS = CreateLocalizedString("en", "US", IccTestDataPrimitives.Unicode_ValRand2); private static readonly IccLocalizedString LocalizedString_Rand_deDE = CreateLocalizedString("de", "DE", IccTestDataPrimitives.Unicode_ValRand3); private static readonly IccLocalizedString LocalizedString_Rand2_deDE = CreateLocalizedString("de", "DE", IccTestDataPrimitives.Unicode_ValRand2); @@ -496,11 +388,13 @@ namespace SixLabors.ImageSharp.Tests LocalizedString_Rand_enUS, LocalizedString_Rand_deDE, }; + private static readonly IccLocalizedString[] LocalizedString_RandArr_en_Invariant = new IccLocalizedString[] { LocalizedString_Rand_en, LocalizedString_Rand_Invariant, }; + private static readonly IccLocalizedString[] LocalizedString_SameArr_enUS_deDE_esXL_xyXL = new IccLocalizedString[] { LocalizedString_Rand_enUS, @@ -510,82 +404,60 @@ namespace SixLabors.ImageSharp.Tests }; public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_enUS_deDE); - public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); + IccTestDataPrimitives.Unicode_Rand3); public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val2 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_en_Invariant); - public static readonly byte[] MultiLocalizedUnicode_Arr2_Read = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr2_Read = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); + IccTestDataPrimitives.Unicode_Rand3); - public static readonly byte[] MultiLocalizedUnicode_Arr2_Write = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr2_Write = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'x', (byte)'x', 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); + IccTestDataPrimitives.Unicode_Rand3); public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val3 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_SameArr_enUS_deDE_esXL_xyXL); - public static readonly byte[] MultiLocalizedUnicode_Arr3 = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr3 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_4, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - new byte[] { (byte)'e', (byte)'s', (byte)'X', (byte)'L' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - new byte[] { (byte)'x', (byte)'y', (byte)'X', (byte)'L' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - - IccTestDataPrimitives.Unicode_Rand2 - ); + IccTestDataPrimitives.Unicode_Rand2); public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Read = { @@ -601,55 +473,36 @@ namespace SixLabors.ImageSharp.Tests new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, }; - #endregion - - #region MultiProcessElementsTagDataEntry - - public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry - ( + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry( new IccMultiProcessElement[] { - IccTestDataMultiProcessElement.MPE_ValCLUT, - IccTestDataMultiProcessElement.MPE_ValCLUT, - } - ); - public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat - ( + IccTestDataMultiProcessElements.MPE_ValCLUT, + IccTestDataMultiProcessElements.MPE_ValCLUT, + }); + + public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt32_2, - new byte[] { 0x00, 0x00, 0x00, 0x20 }, // 32 new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - new byte[] { 0x00, 0x00, 0x00, 0xA4 }, // 164 new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - - IccTestDataMultiProcessElement.MPE_CLUT, - IccTestDataMultiProcessElement.MPE_CLUT - ); + IccTestDataMultiProcessElements.MPE_CLUT, + IccTestDataMultiProcessElements.MPE_CLUT); public static readonly object[][] MultiProcessElementsTagDataEntryTestData = { new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, }; - #endregion - - #region NamedColor2TagDataEntry - - public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry - ( + public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry( 16909060, - ArrayHelper.Fill('A', 31), ArrayHelper.Fill('4', 31), - new IccNamedColor[] - { - IccTestDataNonPrimitives.NamedColor_ValMin, - IccTestDataNonPrimitives.NamedColor_ValMin - } - ); - public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat - ( + ArrayHelper.Fill('A', 31), + ArrayHelper.Fill('4', 31), + new IccNamedColor[] { IccTestDataNonPrimitives.NamedColor_ValMin, IccTestDataNonPrimitives.NamedColor_ValMin }); + + public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat( new byte[] { 0x01, 0x02, 0x03, 0x04 }, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UInt32_3, @@ -658,18 +511,13 @@ namespace SixLabors.ImageSharp.Tests ArrayHelper.Fill((byte)0x34, 31), new byte[] { 0x00 }, IccTestDataNonPrimitives.NamedColor_Min, - IccTestDataNonPrimitives.NamedColor_Min - ); + IccTestDataNonPrimitives.NamedColor_Min); public static readonly object[][] NamedColor2TagDataEntryTestData = { new object[] { NamedColor2_Arr, NamedColor2_Val }, }; - #endregion - - #region ParametricCurveTagDataEntry - public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new IccParametricCurveTagDataEntry(IccTestDataCurves.Parametric_ValVar1); public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; @@ -678,118 +526,81 @@ namespace SixLabors.ImageSharp.Tests new object[] { ParametricCurve_Arr, ParametricCurve_Val }, }; - #endregion - - #region ProfileSequenceDescTagDataEntry - - public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry - ( + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry( new IccProfileDescription[] { IccTestDataNonPrimitives.ProfileDescription_ValRand1, IccTestDataNonPrimitives.ProfileDescription_ValRand1 - } - ); - public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ProfileDescription_Rand1, - IccTestDataNonPrimitives.ProfileDescription_Rand1 - ); + IccTestDataNonPrimitives.ProfileDescription_Rand1); public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = { new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, }; - #endregion - - #region ProfileSequenceIdentifierTagDataEntry - - public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry - ( + public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry( new IccProfileSequenceIdentifier[] { new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), - } - ); - public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat - ( - IccTestDataPrimitives.UInt32_2, + }); + public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x1C }, // 28 new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 - new byte[] { 0x00, 0x00, 0x00, 0x70 }, // 112 new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 - IccTestDataNonPrimitives.ProfileId_Rand, // 16 bytes TagDataEntryHeader_MultiLocalizedUnicodeArr, // 8 bytes MultiLocalizedUnicode_Arr, // 58 bytes new byte[] { 0x00, 0x00 }, // 2 bytes (padding) - IccTestDataNonPrimitives.ProfileId_Rand, TagDataEntryHeader_MultiLocalizedUnicodeArr, MultiLocalizedUnicode_Arr, - new byte[] { 0x00, 0x00 } - ); + new byte[] { 0x00, 0x00 }); public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = { new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, }; - #endregion - - #region ResponseCurveSet16TagDataEntry - - public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry - ( + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry( new IccResponseCurve[] { IccTestDataCurves.Response_ValGrad, IccTestDataCurves.Response_ValGrad, - } - ); - public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt16_2, - new byte[] { 0x00, 0x00, 0x00, 0x14 }, // 20 new byte[] { 0x00, 0x00, 0x00, 0x6C }, // 108 - IccTestDataCurves.Response_Grad, // 88 bytes - IccTestDataCurves.Response_Grad // 88 bytes - ); + IccTestDataCurves.Response_Grad); // 88 bytes public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = { new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, }; - #endregion - - #region Fix16ArrayTagDataEntry - public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new IccFix16ArrayTagDataEntry(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); - public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3 - ); + IccTestDataPrimitives.Fix16_3); public static readonly object[][] Fix16ArrayTagDataEntryTestData = { new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, }; - #endregion - - #region SignatureTagDataEntry - public static readonly IccSignatureTagDataEntry Signature_Val = new IccSignatureTagDataEntry("ABCD"); public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; @@ -798,10 +609,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Signature_Arr, Signature_Val }, }; - #endregion - - #region TextTagDataEntry - public static readonly IccTextTagDataEntry Text_Val = new IccTextTagDataEntry("ABCD"); public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; @@ -810,78 +617,50 @@ namespace SixLabors.ImageSharp.Tests new object[] { Text_Arr, Text_Val, 12u }, }; - #endregion - - #region UFix16ArrayTagDataEntry - public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new IccUFix16ArrayTagDataEntry(new float[] { 1, 2, 3 }); - public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UFix16_1, IccTestDataPrimitives.UFix16_2, - IccTestDataPrimitives.UFix16_3 - ); + IccTestDataPrimitives.UFix16_3); public static readonly object[][] UFix16ArrayTagDataEntryTestData = { new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, }; - #endregion - - #region UInt16ArrayTagDataEntry - public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new IccUInt16ArrayTagDataEntry(new ushort[] { 1, 2, 3 }); - public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); public static readonly object[][] UInt16ArrayTagDataEntryTestData = { new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, }; - #endregion - - #region UInt32ArrayTagDataEntry - public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new IccUInt32ArrayTagDataEntry(new uint[] { 1, 2, 3 }); - public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UInt32_3 - ); + IccTestDataPrimitives.UInt32_3); public static readonly object[][] UInt32ArrayTagDataEntryTestData = { new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, }; - #endregion - - #region UInt64ArrayTagDataEntry - public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new IccUInt64ArrayTagDataEntry(new ulong[] { 1, 2, 3 }); - public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt64_1, IccTestDataPrimitives.UInt64_2, - IccTestDataPrimitives.UInt64_3 - ); + IccTestDataPrimitives.UInt64_3); public static readonly object[][] UInt64ArrayTagDataEntryTestData = { new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, }; - #endregion - - #region UInt8ArrayTagDataEntry - public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new IccUInt8ArrayTagDataEntry(new byte[] { 1, 2, 3 }); public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; @@ -890,88 +669,66 @@ namespace SixLabors.ImageSharp.Tests new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, }; - #endregion - - #region ViewingConditionsTagDataEntry - - public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry - ( + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry( IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccStandardIlluminant.D50 - ); - public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat - ( + IccStandardIlluminant.D50); + + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat( IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataPrimitives.UInt32_1 - ); + IccTestDataPrimitives.UInt32_1); public static readonly object[][] ViewingConditionsTagDataEntryTestData = { new object[] { ViewingConditions_Arr, ViewingConditions_Val }, }; - #endregion - - #region XYZTagDataEntry - public static readonly IccXyzTagDataEntry XYZ_Val = new IccXyzTagDataEntry(new Vector3[] { IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, IccTestDataNonPrimitives.XyzNumber_ValVar3, }); - public static readonly byte[] XYZ_Arr = ArrayHelper.Concat - ( + + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat( IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataNonPrimitives.XyzNumber_Var3 - ); + IccTestDataNonPrimitives.XyzNumber_Var3); public static readonly object[][] XYZTagDataEntryTestData = { new object[] { XYZ_Arr, XYZ_Val, 44u }, }; - #endregion - - #region TextDescriptionTagDataEntry + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand, + IccTestDataPrimitives.Unicode_ValRand1, + ArrayHelper.Fill('A', 66), + 1701729619, + 2); - public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry - ( - IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), - 1701729619, 2 - ); - public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat - ( - new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 IccTestDataPrimitives.Ascii_Rand, - new byte[] { 0x00 }, // Null terminator - - new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00 }, // Null terminator + new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 IccTestDataPrimitives.Unicode_Rand1, - new byte[] { 0x00, 0x00 }, // Null terminator - - new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + new byte[] { 0x00, 0x00 }, // Null terminator + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 } // Null terminator - ); + new byte[] { 0x00 }); // Null terminator public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new IccTextDescriptionTagDataEntry(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); - public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat - ( + public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 IccTestDataPrimitives.Ascii_Rand, new byte[] { 0x00 }, // Null terminator - IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, - new byte[] { 0x00, 0x00, 0x00 }, // 0, 0 - ArrayHelper.Fill((byte)0x00, 67) - ); + ArrayHelper.Fill((byte)0x00, 67)); public static readonly object[][] TextDescriptionTagDataEntryTestData = { @@ -979,19 +736,14 @@ namespace SixLabors.ImageSharp.Tests new object[] { TextDescription_Arr2, TextDescription_Val2 }, }; - #endregion - - #region CrdInfoTagDataEntry - public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new IccCrdInfoTagDataEntry( IccTestDataPrimitives.Ascii_ValRand4, IccTestDataPrimitives.Ascii_ValRand1, IccTestDataPrimitives.Ascii_ValRand2, IccTestDataPrimitives.Ascii_ValRand3, - IccTestDataPrimitives.Ascii_ValRand4 - ); - public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat - ( + IccTestDataPrimitives.Ascii_ValRand4); + + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.Ascii_Rand4, new byte[] { 0 }, @@ -1006,108 +758,74 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0 }, IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.Ascii_Rand4, - new byte[] { 0 } - ); + new byte[] { 0 }); public static readonly object[][] CrdInfoTagDataEntryTestData = { new object[] { CrdInfo_Arr, CrdInfo_Val }, }; - #endregion - - #region ScreeningTagDataEntry - public static readonly IccScreeningTagDataEntry Screening_Val = new IccScreeningTagDataEntry( IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, - new IccScreeningChannel[] - { - IccTestDataNonPrimitives.ScreeningChannel_ValRand1, - IccTestDataNonPrimitives.ScreeningChannel_ValRand2, - } - ); - public static readonly byte[] Screening_Arr = ArrayHelper.Concat - ( + new IccScreeningChannel[] { IccTestDataNonPrimitives.ScreeningChannel_ValRand1, IccTestDataNonPrimitives.ScreeningChannel_ValRand2 }); + + public static readonly byte[] Screening_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int32_1, IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ScreeningChannel_Rand1, - IccTestDataNonPrimitives.ScreeningChannel_Rand2 - ); + IccTestDataNonPrimitives.ScreeningChannel_Rand2); public static readonly object[][] ScreeningTagDataEntryTestData = { new object[] { Screening_Arr, Screening_Val }, }; - #endregion - - #region UcrBgTagDataEntry - public static readonly IccUcrBgTagDataEntry UcrBg_Val = new IccUcrBgTagDataEntry( new ushort[] { 3, 4, 6 }, new ushort[] { 9, 7, 2, 5 }, - IccTestDataPrimitives.Ascii_ValRand - ); - public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat - ( + IccTestDataPrimitives.Ascii_ValRand); + + public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_3, IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.UInt16_6, - IccTestDataPrimitives.UInt32_4, IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.Ascii_Rand, - new byte[] { 0 } - ); + new byte[] { 0 }); public static readonly object[][] UcrBgTagDataEntryTestData = { new object[] { UcrBg_Arr, UcrBg_Val, 41 }, }; - #endregion - - #region TagDataEntry - public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; - public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat - ( + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, Curve_Arr_2, - new byte[] { 0x00, 0x00 } // padding - ); + new byte[] { 0x00, 0x00 }); // padding public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; - public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat - ( + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat( TagDataEntryHeader_MultiLocalizedUnicodeArr, MultiLocalizedUnicode_Arr, - new byte[] { 0x00, 0x00 } // padding - ); + new byte[] { 0x00, 0x00 }); // padding - public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry - ( - IccProfileTag.Unknown, 0, - (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2 - ); + public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry( + IccProfileTag.Unknown, + 0, + (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2); - public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry - ( - IccProfileTag.Unknown, 0, - (uint)TagDataEntry_CurveArr.Length - 2 - ); + public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry(IccProfileTag.Unknown, 0, (uint)TagDataEntry_CurveArr.Length - 2); public static readonly object[][] TagDataEntryTestData = { new object[] { TagDataEntry_CurveArr, TagDataEntry_CurveVal }, new object[] { TagDataEntry_MultiLocalizedUnicodeArr, TagDataEntry_MultiLocalizedUnicodeVal }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 8219920909..bd185fa6b2 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests /// The "Formats" directory, as lazy value /// // ReSharper disable once InconsistentNaming - private static readonly Lazy inputImagesDirectory = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); + private static readonly Lazy InputImagesDirectoryValue = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); /// /// The image (lazy initialized value) @@ -52,17 +52,17 @@ namespace SixLabors.ImageSharp.Tests public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); /// - /// The full path to file. + /// Gets the full path to file. /// public string FullPath { get; } /// - /// The file name. + /// Gets the file name. /// public string FileName => Path.GetFileName(this.FullPath); /// - /// The file name without extension. + /// Gets the file name without extension. /// public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the input image directory. /// - private static string InputImagesDirectory => inputImagesDirectory.Value; + private static string InputImagesDirectory => InputImagesDirectoryValue.Value; /// /// Gets the full qualified path to the input test file. diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs deleted file mode 100644 index c01f50f203..0000000000 --- a/tests/ImageSharp.Tests/TestFont.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// A test image file. - /// - public static class TestFontUtilities - { - /// - /// The formats directory. - /// - private static readonly string FormatsDirectory = GetFontsDirectory(); - - /// - /// Gets the full qualified path to the file. - /// - /// - /// The file path. - /// - /// - /// The . - /// - public static string GetPath(string file) - { - return Path.Combine(FormatsDirectory, file); - } - - /// - /// Gets the correct path to the formats directory. - /// - /// - /// The . - /// - private static string GetFontsDirectory() - { - List directories = new List { - "TestFonts/", // Here for code coverage tests. - "tests/ImageSharp.Tests/TestFonts/", // from travis/build script - "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 - "../../../../TestFonts/" - }; - - directories = directories.SelectMany(x => new[] - { - Path.GetFullPath(x) - }).ToList(); - - AddFormatsDirectoryFromTestAssemblyPath(directories); - - string directory = directories.FirstOrDefault(Directory.Exists); - - if (directory != null) - { - return directory; - } - - throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); - } - - /// - /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory - /// in certain scenarios like dotTrace test profiling. - /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. - /// - /// The directories list - private static void AddFormatsDirectoryFromTestAssemblyPath(List directories) - { - string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; - assemblyLocation = Path.GetDirectoryName(assemblyLocation); - - if (assemblyLocation != null) - { - string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); - dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); - directories.Add(dirFromAssemblyLocation); - } - } - } -} diff --git a/tests/ImageSharp.Tests/TestFontUtilities.cs b/tests/ImageSharp.Tests/TestFontUtilities.cs new file mode 100644 index 0000000000..e087516c68 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFontUtilities.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// A test image file. + /// + public static class TestFontUtilities + { + /// + /// The formats directory. + /// + private static readonly string FormatsDirectory = GetFontsDirectory(); + + /// + /// Gets the full qualified path to the file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetPath(string file) + { + return Path.Combine(FormatsDirectory, file); + } + + /// + /// Gets the correct path to the formats directory. + /// + /// + /// The . + /// + private static string GetFontsDirectory() + { + List directories = new List + { + "TestFonts/", // Here for code coverage tests. + "tests/ImageSharp.Tests/TestFonts/", // from travis/build script + "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../TestFonts/" + }; + + directories = directories.SelectMany(x => new[] + { + Path.GetFullPath(x) + }).ToList(); + + AddFormatsDirectoryFromTestAssemblyPath(directories); + + string directory = directories.FirstOrDefault(Directory.Exists); + + if (directory != null) + { + return directory; + } + + throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); + } + + /// + /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory + /// in certain scenarios like dotTrace test profiling. + /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. + /// + /// The directories list + private static void AddFormatsDirectoryFromTestAssemblyPath(List directories) + { + string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; + assemblyLocation = Path.GetDirectoryName(assemblyLocation); + + if (assemblyLocation != null) + { + string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); + dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); + directories.Add(dirFromAssemblyLocation); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 0a56110560..783d6fa48b 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -47,16 +47,16 @@ namespace SixLabors.ImageSharp.Tests { ms.Write(marker, 0, marker.Length); } + ms.Position = 0; return ms; } public void VerifySpecificDecodeCall(byte[] marker, Configuration config) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); - Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) @@ -69,7 +69,6 @@ namespace SixLabors.ImageSharp.Tests { DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); - Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) @@ -79,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests } public Image Sample() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { lock (this.sampleImages) { @@ -116,6 +115,7 @@ namespace SixLabors.ImageSharp.Tests { return false; } + for (int i = 0; i < this.header.Length; i++) { if (header[i] != this.header[i]) @@ -123,6 +123,7 @@ namespace SixLabors.ImageSharp.Tests return false; } } + return true; } @@ -135,38 +136,37 @@ namespace SixLabors.ImageSharp.Tests public struct DecodeOperation { - public byte[] marker; - internal Configuration config; + public byte[] Marker; + internal Configuration Config; - public Type pixelType; + public Type PixelType; public bool IsMatch(byte[] testMarker, Configuration config, Type pixelType) { - - if (this.config != config || this.pixelType != pixelType) + if (this.Config != config || this.PixelType != pixelType) { return false; } - if (testMarker.Length != this.marker.Length) + if (testMarker.Length != this.Marker.Length) { return false; } - for (int i = 0; i < this.marker.Length; i++) + for (int i = 0; i < this.Marker.Length; i++) { - if (testMarker[i] != this.marker[i]) + if (testMarker[i] != this.Marker[i]) { return false; } } + return true; } } public class TestHeader : IImageFormatDetector { - private TestFormat testFormat; public int HeaderSize => this.testFormat.HeaderSize; @@ -174,7 +174,9 @@ namespace SixLabors.ImageSharp.Tests public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.testFormat.IsSupportedFileFormat(header)) + { return this.testFormat; + } return null; } @@ -184,6 +186,7 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } } + public class TestDecoder : ImageSharp.Formats.IImageDecoder { private TestFormat testFormat; @@ -193,30 +196,30 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } - public IEnumerable MimeTypes => new[] { testFormat.MimeType }; - - public IEnumerable FileExtensions => testFormat.SupportedExtensions; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; - public int HeaderSize => testFormat.HeaderSize; + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public Image Decode(Configuration config, Stream stream) where TPixel : struct, IPixel + public int HeaderSize => this.testFormat.HeaderSize; + public Image Decode(Configuration config, Stream stream) + where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); stream.CopyTo(ms); var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { - marker = marker, - config = config, - pixelType = typeof(TPixel) + Marker = marker, + Config = config, + PixelType = typeof(TPixel) }); // TODO record this happened so we can verify it. return this.testFormat.Sample(); } - public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); + public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } @@ -230,35 +233,85 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } - public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; - public IEnumerable FileExtensions => testFormat.SupportedExtensions; + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public void Encode(Image image, Stream stream) where TPixel : struct, IPixel + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { // TODO record this happened so we can verify it. } } - - struct TestPixelForAgnosticDecode : IPixel + public struct TestPixelForAgnosticDecode : IPixel { public PixelOperations CreatePixelOperations() => new PixelOperations(); - public void FromScaledVector4(Vector4 vector) { } + + public void FromScaledVector4(Vector4 vector) + { + } + public Vector4 ToScaledVector4() => default; - public void FromVector4(Vector4 vector) { } + + public void FromVector4(Vector4 vector) + { + } + public Vector4 ToVector4() => default; - public void FromArgb32(Argb32 source) { } - public void FromBgra5551(Bgra5551 source) { } - public void FromBgr24(Bgr24 source) { } - public void FromBgra32(Bgra32 source) { } - public void FromGray8(Gray8 source) { } - public void FromGray16(Gray16 source) { } - public void FromRgb24(Rgb24 source) { } - public void FromRgba32(Rgba32 source) { } - public void ToRgba32(ref Rgba32 dest) { } - public void FromRgb48(Rgb48 source) { } - public void FromRgba64(Rgba64 source) { } + + public void FromArgb32(Argb32 source) + { + } + + public void FromBgra5551(Bgra5551 source) + { + } + + public void FromBgr24(Bgr24 source) + { + } + + public void FromBgra32(Bgra32 source) + { + } + + public void FromL8(L8 source) + { + } + + public void FromL16(L16 source) + { + } + + public void FromLa16(La16 source) + { + } + + public void FromLa32(La32 source) + { + } + + public void FromRgb24(Rgb24 source) + { + } + + public void FromRgba32(Rgba32 source) + { + } + + public void ToRgba32(ref Rgba32 dest) + { + } + + public void FromRgb48(Rgb48 source) + { + } + + public void FromRgba64(Rgba64 source) + { + } + public bool Equals(TestPixelForAgnosticDecode other) => false; } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 163d09bdde..141a8d1c38 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.Linq; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming // ReSharper disable MemberHidesStaticFromOuterClass namespace SixLabors.ImageSharp.Tests { @@ -27,9 +27,9 @@ namespace SixLabors.ImageSharp.Tests public const string Palette8Bpp = "Png/palette-8bpp.png"; public const string Bpp1 = "Png/bpp1.png"; public const string Gray4Bpp = "Png/gray_4bpp.png"; - public const string Gray16Bit = "Png/gray-16.png"; - public const string GrayAlpha8Bit = "Png/gray-alpha-8.png"; - public const string GrayAlpha8BitInterlaced = "Png/rollsroyce.png"; + public const string L16Bit = "Png/gray-16.png"; + public const string GrayA8Bit = "Png/gray-alpha-8.png"; + public const string GrayA8BitInterlaced = "Png/rollsroyce.png"; public const string GrayAlpha1BitInterlaced = "Png/iftbbn0g01.png"; public const string GrayAlpha2BitInterlaced = "Png/iftbbn0g02.png"; public const string Gray4BitInterlaced = "Png/iftbbn0g04.png"; @@ -52,10 +52,11 @@ namespace SixLabors.ImageSharp.Tests public const string Gray1BitTrans = "Png/gray-1-trns.png"; public const string Gray2BitTrans = "Png/gray-2-tRNS.png"; public const string Gray4BitTrans = "Png/gray-4-tRNS.png"; - public const string Gray8BitTrans = "Png/gray-8-tRNS.png"; + public const string L8BitTrans = "Png/gray-8-tRNS.png"; public const string LowColorVariance = "Png/low-variance.png"; - public const string PngWithMetaData = "Png/PngWithMetaData.png"; + public const string PngWithMetadata = "Png/PngWithMetaData.png"; public const string InvalidTextData = "Png/InvalidTextData.png"; + public const string David = "Png/david.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -64,6 +65,12 @@ namespace SixLabors.ImageSharp.Tests public const string Filter3 = "Png/filter3.png"; public const string Filter4 = "Png/filter4.png"; + // Paletted images also from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + public const string PalettedTwoColor = "Png/basn3p01.png"; + public const string PalettedFourColor = "Png/basn3p02.png"; + public const string PalettedSixteenColor = "Png/basn3p04.png"; + public const string Paletted256Colors = "Png/basn3p08.png"; + // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; @@ -82,15 +89,51 @@ namespace SixLabors.ImageSharp.Tests public const string Ducky = "Png/ducky.png"; public const string Rainbow = "Png/rainbow.png"; + public const string Bradley01 = "Png/Bradley01.png"; + public const string Bradley02 = "Png/Bradley02.png"; + + // Issue 1014: https://github.com/SixLabors/ImageSharp/issues/1014 + public const string Issue1014_1 = "Png/issues/Issue_1014_1.png"; + public const string Issue1014_2 = "Png/issues/Issue_1014_2.png"; + public const string Issue1014_3 = "Png/issues/Issue_1014_3.png"; + public const string Issue1014_4 = "Png/issues/Issue_1014_4.png"; + public const string Issue1014_5 = "Png/issues/Issue_1014_5.png"; + public const string Issue1014_6 = "Png/issues/Issue_1014_6.png"; + + // Issue 1127: https://github.com/SixLabors/ImageSharp/issues/1127 + public const string Issue1127 = "Png/issues/Issue_1127.png"; + + // Issue 1177: https://github.com/SixLabors/ImageSharp/issues/1177 + public const string Issue1177_1 = "Png/issues/Issue_1177_1.png"; + public const string Issue1177_2 = "Png/issues/Issue_1177_2.png"; + public static class Bad { - // Odd chunk lengths - public const string ChunkLength1 = "Png/chunklength1.png"; - public const string ChunkLength2 = "Png/chunklength2.png"; + public const string MissingDataChunk = "Png/xdtn0g01.png"; public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; + + // Zlib errors. public const string ZlibOverflow = "Png/zlib-overflow.png"; public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png"; + + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; + + // Issue 1047: https://github.com/SixLabors/ImageSharp/issues/1047 + public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png"; + + // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 + public const string Issue410_MalformedApplePng = "Png/issues/Issue_410.png"; + + // Bad bit depth. + public const string BitDepthZero = "Png/xd0n2c08.png"; + public const string BitDepthThree = "Png/xd3n2c08.png"; + + // Invalid color type. + public const string ColorTypeOne = "Png/xc1n0g08.png"; + public const string ColorTypeNine = "Png/xc9n2c08.png"; } public static readonly string[] All = @@ -133,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; public const string Ycck = "Jpg/baseline/ycck.jpg"; - public const string Turtle = "Jpg/baseline/turtle.jpg"; + public const string Turtle420 = "Jpg/baseline/turtle.jpg"; public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.jpg"; public const string Hiyamugi = "Jpg/baseline/Hiyamugi.jpg"; public const string Snake = "Jpg/baseline/Snake.jpg"; @@ -148,11 +191,13 @@ namespace SixLabors.ImageSharp.Tests public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg"; public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg"; + public const string Iptc = "Jpg/baseline/iptc.jpg"; + public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg"; public static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle, GammaDalaiLamaGray, + Calliphora, Turtle420, GammaDalaiLamaGray, Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1, Testorig12bit, YcckSubsample1222 }; @@ -181,6 +226,9 @@ namespace SixLabors.ImageSharp.Tests public const string ExifGetString750Load = "Jpg/issues/issue750-exif-load.jpg"; public const string IncorrectQuality845 = "Jpg/issues/Issue845-Incorrect-Quality99.jpg"; public const string IncorrectColorspace855 = "Jpg/issues/issue855-incorrect-colorspace.jpg"; + public const string IncorrectResize1006 = "Jpg/issues/issue1006-incorrect-resize.jpg"; + public const string ExifResize1049 = "Jpg/issues/issue1049-exif-resize.jpg"; + public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg"; public static class Fuzz { @@ -298,8 +346,8 @@ namespace SixLabors.ImageSharp.Tests public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; - public static readonly string[] BitFields - = { + public static readonly string[] BitFields = + { Rgb32bfdef, Rgb32bf, Rgb16565, @@ -308,32 +356,32 @@ namespace SixLabors.ImageSharp.Tests Issue735, }; - public static readonly string[] Miscellaneous - = { + public static readonly string[] Miscellaneous = + { Car, F, NegHeight }; - public static readonly string[] Benchmark - = { - Car, - F, - NegHeight, - CoreHeader, - V5Header, - RLE4, - RLE8, - RLE8Inverted, - Bit1, - Bit1Pal1, - Bit4, - Bit8, - Bit8Inverted, - Bit16, - Bit16Inverted, - Bit32Rgb - }; + public static readonly string[] Benchmark = + { + Car, + F, + NegHeight, + CoreHeader, + V5Header, + RLE4, + RLE8, + RLE8Inverted, + Bit1, + Bit1Pal1, + Bit4, + Bit8, + Bit8Inverted, + Bit16, + Bit16Inverted, + Bit32Rgb + }; } public static class Gif @@ -341,6 +389,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rings = "Gif/rings.gif"; public const string Giphy = "Gif/giphy.gif"; public const string Cheers = "Gif/cheers.gif"; + public const string Receipt = "Gif/receipt.gif"; public const string Trans = "Gif/trans.gif"; public const string Kumin = "Gif/kumin.gif"; public const string Leo = "Gif/leo.gif"; @@ -348,6 +397,13 @@ namespace SixLabors.ImageSharp.Tests public const string Ratio1x4 = "Gif/base_1x4.gif"; public const string LargeComment = "Gif/large_comment.gif"; + // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite + public const string ZeroSize = "Gif/image-zero-size.gif"; + public const string ZeroHeight = "Gif/image-zero-height.gif"; + public const string ZeroWidth = "Gif/image-zero-width.gif"; + public const string MaxWidth = "Gif/max-width.gif"; + public const string MaxHeight = "Gif/max-height.gif"; + public static class Issues { public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; @@ -357,5 +413,80 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; } + + public static class Tga + { + public const string Gray8BitTopLeft = "Tga/grayscale_UL.tga"; + public const string Gray8BitTopRight = "Tga/grayscale_UR.tga"; + public const string Gray8BitBottomLeft = "Tga/targa_8bit.tga"; + public const string Gray8BitBottomRight = "Tga/grayscale_LR.tga"; + + public const string Gray8BitRleTopLeft = "Tga/grayscale_rle_UL.tga"; + public const string Gray8BitRleTopRight = "Tga/grayscale_rle_UR.tga"; + public const string Gray8BitRleBottomLeft = "Tga/targa_8bit_rle.tga"; + public const string Gray8BitRleBottomRight = "Tga/grayscale_rle_LR.tga"; + + public const string Bit15 = "Tga/rgb15.tga"; + public const string Bit15Rle = "Tga/rgb15rle.tga"; + public const string Bit16BottomLeft = "Tga/targa_16bit.tga"; + public const string Bit16PalRle = "Tga/ccm8.tga"; + public const string Bit16RleBottomLeft = "Tga/targa_16bit_rle.tga"; + public const string Bit16PalBottomLeft = "Tga/targa_16bit_pal.tga"; + + public const string Gray16BitTopLeft = "Tga/grayscale_a_UL.tga"; + public const string Gray16BitBottomLeft = "Tga/grayscale_a_LL.tga"; + public const string Gray16BitBottomRight = "Tga/grayscale_a_LR.tga"; + public const string Gray16BitTopRight = "Tga/grayscale_a_UR.tga"; + + public const string Gray16BitRleTopLeft = "Tga/grayscale_a_rle_UL.tga"; + public const string Gray16BitRleBottomLeft = "Tga/grayscale_a_rle_LL.tga"; + public const string Gray16BitRleBottomRight = "Tga/grayscale_a_rle_LR.tga"; + public const string Gray16BitRleTopRight = "Tga/grayscale_a_rle_UR.tga"; + + public const string Bit24TopLeft = "Tga/rgb24_top_left.tga"; + public const string Bit24BottomLeft = "Tga/targa_24bit.tga"; + public const string Bit24BottomRight = "Tga/rgb_LR.tga"; + public const string Bit24TopRight = "Tga/rgb_UR.tga"; + + public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga"; + public const string Bit24RleBottomLeft = "Tga/targa_24bit_rle.tga"; + public const string Bit24RleTopRight = "Tga/rgb_rle_UR.tga"; + public const string Bit24RleBottomRight = "Tga/rgb_rle_LR.tga"; + + public const string Bit24PalTopLeft = "Tga/targa_24bit_pal_origin_topleft.tga"; + public const string Bit24PalTopRight = "Tga/indexed_UR.tga"; + public const string Bit24PalBottomLeft = "Tga/targa_24bit_pal.tga"; + public const string Bit24PalBottomRight = "Tga/indexed_LR.tga"; + + public const string Bit24PalRleTopLeft = "Tga/indexed_rle_UL.tga"; + public const string Bit24PalRleBottomLeft = "Tga/indexed_rle_LL.tga"; + public const string Bit24PalRleTopRight = "Tga/indexed_rle_UR.tga"; + public const string Bit24PalRleBottomRight = "Tga/indexed_rle_LR.tga"; + + public const string Bit32TopLeft = "Tga/rgb_a_UL.tga"; + public const string Bit32BottomLeft = "Tga/targa_32bit.tga"; + public const string Bit32TopRight = "Tga/rgb_a_UR.tga"; + public const string Bit32BottomRight = "Tga/rgb_a_LR.tga"; + + public const string Bit32PalTopLeft = "Tga/indexed_a_UL.tga"; + public const string Bit32PalBottomLeft = "Tga/indexed_a_LL.tga"; + public const string Bit32PalBottomRight = "Tga/indexed_a_LR.tga"; + public const string Bit32PalTopRight = "Tga/indexed_a_UR.tga"; + + public const string Bit32RleTopLeft = "Tga/rgb_a_rle_UL.tga"; + public const string Bit32RleTopRight = "Tga/rgb_a_rle_UR.tga"; + public const string Bit32RleBottomRight = "Tga/rgb_a_rle_LR.tga"; + public const string Bit32RleBottomLeft = "Tga/targa_32bit_rle.tga"; + + public const string Bit32PalRleTopLeft = "Tga/indexed_a_rle_UL.tga"; + public const string Bit32PalRleBottomLeft = "Tga/indexed_a_rle_LL.tga"; + public const string Bit32PalRleTopRight = "Tga/indexed_a_rle_UR.tga"; + public const string Bit32PalRleBottomRight = "Tga/indexed_a_rle_LR.tga"; + + public const string NoAlphaBits16Bit = "Tga/16bit_noalphabits.tga"; + public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga"; + public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; + public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 872a935ffe..eff29555c8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Numerics; -using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Tests { @@ -16,20 +15,20 @@ namespace SixLabors.ImageSharp.Tests IEqualityComparer, IEqualityComparer { - private readonly float Epsilon; + private readonly float epsilon; /// /// Initializes a new instance of the class. /// /// The comparison error difference epsilon to use. - public ApproximateFloatComparer(float epsilon = 1F) => this.Epsilon = epsilon; + public ApproximateFloatComparer(float epsilon = 1F) => this.epsilon = epsilon; /// public bool Equals(float x, float y) { float d = x - y; - return d >= -this.Epsilon && d <= this.Epsilon; + return d >= -this.epsilon && d <= this.epsilon; } /// @@ -61,4 +60,4 @@ namespace SixLabors.ImageSharp.Tests /// public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs index fdb694dccb..eceecb2c83 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp.Tests arrays[i].CopyTo(result, offset); offset += arrays[i].Length; } + return result; } @@ -39,6 +40,7 @@ namespace SixLabors.ImageSharp.Tests { result[i] = value; } + return result; } @@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests /// The filled string public static string Fill(char value, int length) { - return "".PadRight(length, value); + return string.Empty.PadRight(length, value); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs index b2967058c0..3287311bf1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Tests +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests { using System; @@ -14,4 +17,4 @@ public string Subfolder { get; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index f03d68307b..85b178c733 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -18,12 +18,16 @@ namespace SixLabors.ImageSharp.Tests protected readonly PixelTypes PixelTypes; + static ImageDataAttributeBase() + { + // ImageDataAttributes are used in almost all tests, thus a good place to enforce the execution of + // TestEnvironment static constructor before anything else is done. + TestEnvironment.EnsureSharedInitializersDone(); + } + /// /// Initializes a new instance of the class. /// - /// - /// - /// protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) { this.PixelTypes = pixelTypes; @@ -32,12 +36,12 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Gets the member name + /// Gets the member name. /// public string MemberName { get; } /// - /// Gets the member type + /// Gets or sets the member type. /// public Type MemberType { get; set; } @@ -84,8 +88,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns a value indicating whether the first parameter of the method is a test provider. /// - /// - /// + /// True, if the first parameter is a test provider. private bool FirstIsProvider(MethodInfo testMethod) { TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); @@ -102,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests { foreach (object[] row in memberData) { - var actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; + object[] actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; Array.Copy(originalFactoryMethodArgs, actualFactoryMethodArgs, originalFactoryMethodArgs.Length); actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; @@ -110,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) .Invoke(null, actualFactoryMethodArgs); - var result = new object[this.AdditionalParameters.Length + 1 + row.Length]; + object[] result = new object[this.AdditionalParameters.Length + 1 + row.Length]; result[0] = factory; Array.Copy(row, 0, result, 1, row.Length); Array.Copy(this.AdditionalParameters, 0, result, 1 + row.Length, this.AdditionalParameters.Length); @@ -153,6 +156,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the field accessor for the given type. /// + /// The field accessor. protected Func GetFieldAccessor(Type type, string memberName) { FieldInfo fieldInfo = null; @@ -160,11 +164,15 @@ namespace SixLabors.ImageSharp.Tests { fieldInfo = reflectionType.GetRuntimeField(memberName); if (fieldInfo != null) + { break; + } } - if (fieldInfo == null || !fieldInfo.IsStatic) + if (fieldInfo is null || !fieldInfo.IsStatic) + { return null; + } return () => fieldInfo.GetValue(null); } @@ -172,6 +180,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the property accessor for the given type. /// + /// The property accessor. protected Func GetPropertyAccessor(Type type, string memberName) { PropertyInfo propInfo = null; @@ -184,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests } } - if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic) + if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) { return null; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs deleted file mode 100644 index 796cba8554..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Reflection; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Triggers passing instances which produce a blank image of size width * height. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - public class WithBlankImagesAttribute : ImageDataAttributeBase - { - /// - /// Triggers passing an that produces a blank image of size width * height - /// - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Triggers passing an that produces a blank image of size width * height - /// - /// The member data - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithBlankImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - public int Width { get; } - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "Blank"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs new file mode 100644 index 0000000000..051bfecdcf --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Triggers passing instances which produce a blank image of size width * height. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithBlankImagesAttribute : ImageDataAttributeBase + { + /// + /// Triggers passing an that produces a blank image of size width * height + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Triggers passing an that produces a blank image of size width * height + /// + /// The member data + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + public int Width { get; } + + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Blank"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs index cdf5fcb089..60c8c8cefc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs @@ -9,8 +9,8 @@ namespace SixLabors.ImageSharp.Tests { /// /// Triggers passing instances which return the image produced by the given test class member method - /// instances will be passed for each the pixel format defined by the pixelTypes parameter - /// The parameter of the factory method must be a instance + /// instances will be passed for each the pixel format defined by the pixelTypes parameter. + /// The parameter of the factory method must be a instance. /// public class WithMemberFactoryAttribute : ImageDataAttributeBase { @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Tests /// /// Triggers passing instances which return the image produced by the given test class member method - /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// instances will be passed for each the pixel format defined by the pixelTypes parameter. /// - /// The name of the static test class which returns the image - /// The requested pixel types - /// Additional theory parameter values + /// The name of the static test class which returns the image. + /// The requested pixel types. + /// Additional theory parameter values. public WithMemberFactoryAttribute(string memberMethodName, PixelTypes pixelTypes, params object[] additionalParameters) : base(null, pixelTypes, additionalParameters) { @@ -31,21 +31,9 @@ namespace SixLabors.ImageSharp.Tests protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - MethodInfo m = testMethod.DeclaringType.GetMethod(this.memberMethodName); - - Type[] args = factoryType.GetGenericArguments(); - Type colorType = args.Single(); - - Type imgType = typeof(Image<>).MakeGenericType(colorType); - - Type funcType = typeof(Func<>).MakeGenericType(imgType); - - MethodInfo genericMethod = m.MakeGenericMethod(args); - - Delegate d = genericMethod.CreateDelegate(funcType); - return new object[] { d }; + return new object[] { testMethod.DeclaringType.FullName, this.memberMethodName }; } protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda"; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs index 190e80fbad..5c67b5d138 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -141,22 +141,22 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Red + /// Gets the red component. /// public byte R { get; } /// - /// Green + /// Gets the green component. /// public byte G { get; } /// - /// Blue + /// Gets the blue component. /// public byte B { get; } /// - /// Alpha + /// Gets the alpha component. /// public byte A { get; } @@ -165,4 +165,4 @@ namespace SixLabors.ImageSharp.Tests protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs deleted file mode 100644 index 7c659c64fc..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Reflection; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Triggers passing instances which produce a blank image of size width * height. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - public class WithTestPatternImagesAttribute : ImageDataAttributeBase - { - /// - /// Triggers passing an that produces a test pattern image of size width * height - /// - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : this(null, width, height, pixelTypes, additionalParameters) - { - } - - /// - /// Triggers passing an that produces a test pattern image of size width * height - /// - /// The member data to apply to theories - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Gets the width - /// - public int Width { get; } - - /// - /// Gets the height - /// - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs new file mode 100644 index 0000000000..0f00f1d86d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Triggers passing instances which produce a blank image of size width * height. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithTestPatternImagesAttribute : ImageDataAttributeBase + { + /// + /// Initializes a new instance of the class. + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : this(null, width, height, pixelTypes, additionalParameters) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The member data to apply to theories + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Gets the width + /// + public int Width { get; } + + /// + /// Gets the height + /// + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs new file mode 100644 index 0000000000..d7c54f35f2 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + /// + /// RemoteExecutor can only execute static methods, which can only consume string arguments, + /// because data is being passed on command line interface. This utility allows serialization + /// of types to strings. + /// + internal class BasicSerializer : IXunitSerializationInfo + { + private readonly Dictionary map = new Dictionary(); + + public const char Separator = ':'; + + private string DumpToString(Type type) + { + using var ms = new MemoryStream(); + using var writer = new StreamWriter(ms); + writer.WriteLine(type.FullName); + foreach (KeyValuePair kv in this.map) + { + writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); + } + + writer.Flush(); + byte[] data = ms.ToArray(); + return System.Convert.ToBase64String(data); + } + + private Type LoadDump(string dump) + { + byte[] data = System.Convert.FromBase64String(dump); + + using var ms = new MemoryStream(data); + using var reader = new StreamReader(ms); + var type = Type.GetType(reader.ReadLine()); + for (string s = reader.ReadLine(); s != null; s = reader.ReadLine()) + { + string[] kv = s.Split(Separator); + this.map[kv[0]] = kv[1]; + } + + return type; + } + + public static string Serialize(IXunitSerializable serializable) + { + var serializer = new BasicSerializer(); + serializable.Serialize(serializer); + return serializer.DumpToString(serializable.GetType()); + } + + public static T Deserialize(string dump) + where T : IXunitSerializable + { + var serializer = new BasicSerializer(); + Type type = serializer.LoadDump(dump); + + var result = (T)Activator.CreateInstance(type); + result.Deserialize(serializer); + return result; + } + + public void AddValue(string key, object value, Type type = null) + { + Guard.NotNull(key, nameof(key)); + if (value == null) + { + return; + } + + type ??= value.GetType(); + + this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); + } + + public object GetValue(string key, Type type) + { + Guard.NotNull(key, nameof(key)); + + if (!this.map.TryGetValue(key, out string str)) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); + } + + public T GetValue(string key) => (T)this.GetValue(key, typeof(T)); + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs new file mode 100644 index 0000000000..248755ea36 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + public class GraphicsOptionsComparer : IEqualityComparer + { + public bool Equals(GraphicsOptions x, GraphicsOptions y) + { + return x.AlphaCompositionMode == y.AlphaCompositionMode + && x.Antialias == y.Antialias + && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth + && x.BlendPercentage == y.BlendPercentage + && x.ColorBlendingMode == y.ColorBlendingMode; + } + + public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode(); + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index 59167cc888..9b7ebe34a0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,10 +1,11 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public class ExactImageComparer : ImageComparer @@ -23,12 +24,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison int width = actual.Width; // TODO: Comparing through Rgba64 may not be robust enough because of the existence of super high precision pixel types. - var aBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width]; var differences = new List(); - Configuration configuration = expected.Configuration; + Configuration configuration = expected.GetConfiguration(); for (int y = 0; y < actual.Height; y++) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index d000f70938..626b698e1c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using System.Linq; @@ -21,6 +24,16 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); + // TODO: We should add OSX. + sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux"); + sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is CI : {0}", TestEnvironment.RunsOnCI); + sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); + sb.Append(Environment.NewLine); + int i = 0; foreach (ImageSimilarityReport r in reports) { @@ -29,7 +42,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); i++; } + return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs index 024c2ee215..df1c1837b2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public class ImageDimensionsMismatchException : ImagesSimilarityException @@ -15,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } public Size ExpectedSize { get; } + public Size ActualSize { get; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs index bbdb6b5815..d84f1c358a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { using System; @@ -9,4 +12,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index 38dada063c..76c018f066 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -6,8 +6,6 @@ using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public abstract class ImageComparer @@ -18,6 +16,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// Returns an instance of . /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. /// + /// A ImageComparer instance. public static ImageComparer Tolerant( float imageThreshold = TolerantImageComparer.DefaultImageThreshold, int perPixelManhattanThreshold = 0) @@ -28,13 +27,15 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// /// Returns Tolerant(imageThresholdInPercents/100) /// + /// A ImageComparer instance. public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); public abstract ImageSimilarityReport CompareImagesOrFrames( ImageFrame expected, ImageFrame actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel; + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel; } public static class ImageComparerExtensions @@ -43,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); } @@ -52,7 +54,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { var result = new List>(); @@ -60,6 +63,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { throw new Exception("Frame count does not match!"); } + for (int i = 0; i < expected.Frames.Count; i++) { ImageSimilarityReport report = comparer.CompareImagesOrFrames(expected.Frames[i], actual.Frames[i]); @@ -76,7 +80,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { if (expected.Size() != actual.Size()) { @@ -100,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison Image expected, Image actual, Rectangle ignoredRegion) - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { if (expected.Size() != actual.Size()) { @@ -139,4 +144,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index f534079769..2faeacf683 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -63,6 +66,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendLine(); sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } + int max = Math.Min(5, this.Differences.Length); for (int i = 0; i < max; i++) @@ -73,17 +77,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendFormat(";{0}", Environment.NewLine); } } + if (this.Differences.Length >= 5) { sb.Append("..."); } + return sb.ToString(); } } public class ImageSimilarityReport : ImageSimilarityReport - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { public ImageSimilarityReport( ImageFrame expectedImage, @@ -101,4 +107,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison public new ImageFrame ActualImage => (ImageFrame)base.ActualImage; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs index 1ffeb60ad4..6452bc581a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -1,5 +1,7 @@ -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { @@ -20,7 +22,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) - : this(position, + : this( + position, actual.R - expected.R, actual.G - expected.G, actual.B - expected.B, @@ -31,11 +34,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison public Point Position { get; } public int RedDifference { get; } + public int GreenDifference { get; } + public int BlueDifference { get; } + public int AlphaDifference { get; } public override string ToString() => $"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]"; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 8bed3a7155..36da984533 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -5,8 +8,6 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public class TolerantImageComparer : ImageComparer @@ -67,14 +68,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison int width = actual.Width; // TODO: Comparing through Rgba64 may not robust enough because of the existence of super high precision pixel types. - var aBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width]; float totalDifference = 0F; var differences = new List(); - Configuration configuration = expected.Configuration; + Configuration configuration = expected.GetConfiguration(); for (int y = 0; y < actual.Height; y++) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 63f07eed11..1025ed9a15 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -5,21 +5,32 @@ using System; using System.Numerics; using SixLabors.ImageSharp.Advanced; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider + public abstract partial class TestImageProvider : IXunitSerializable { + public virtual TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + throw new NotSupportedException("GetExpectedBasicTestPatternPixelAt(x,y) only works with BasicTestPattern"); + } + private class BasicTestPatternProvider : BlankProvider { + private static readonly TPixel TopLeftColor = Color.Red.ToPixel(); + private static readonly TPixel TopRightColor = Color.Green.ToPixel(); + private static readonly TPixel BottomLeftColor = Color.Blue.ToPixel(); + + // Transparent purple: + private static readonly TPixel BottomRightColor = GetBottomRightColor(); + public BasicTestPatternProvider(int width, int height) : base(width, height) { } - /// - /// This parameterless constructor is needed for xUnit deserialization - /// + // This parameterless constructor is needed for xUnit deserialization public BasicTestPatternProvider() { } @@ -30,14 +41,6 @@ namespace SixLabors.ImageSharp.Tests { var result = new Image(this.Configuration, this.Width, this.Height); - TPixel topLeftColor = Color.Red.ToPixel(); - TPixel topRightColor = Color.Green.ToPixel(); - TPixel bottomLeftColor = Color.Blue.ToPixel(); - - // Transparent purple: - TPixel bottomRightColor = default; - bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); - int midY = this.Height / 2; int midX = this.Width / 2; @@ -45,20 +48,42 @@ namespace SixLabors.ImageSharp.Tests { Span row = result.GetPixelRowSpan(y); - row.Slice(0, midX).Fill(topLeftColor); - row.Slice(midX, this.Width - midX).Fill(topRightColor); + row.Slice(0, midX).Fill(TopLeftColor); + row.Slice(midX, this.Width - midX).Fill(TopRightColor); } for (int y = midY; y < this.Height; y++) { Span row = result.GetPixelRowSpan(y); - row.Slice(0, midX).Fill(bottomLeftColor); - row.Slice(midX, this.Width - midX).Fill(bottomRightColor); + row.Slice(0, midX).Fill(BottomLeftColor); + row.Slice(midX, this.Width - midX).Fill(BottomRightColor); } return result; } + + public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + int midY = this.Height / 2; + int midX = this.Width / 2; + + if (y < midY) + { + return x < midX ? TopLeftColor : TopRightColor; + } + else + { + return x < midX ? BottomLeftColor : BottomRightColor; + } + } + + private static TPixel GetBottomRightColor() + { + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); + return bottomRightColor; + } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index 0860af1a4e..b8ad5c5069 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,8 +7,8 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class BlankProvider : TestImageProvider, IXunitSerializable { @@ -35,7 +35,6 @@ namespace SixLabors.ImageSharp.Tests public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height); - public override void Deserialize(IXunitSerializationInfo info) { this.Width = info.GetValue("width"); @@ -51,4 +50,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 8c5b88b280..48d7b80fbe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,8 +13,8 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class FileProvider : TestImageProvider, IXunitSerializable { @@ -22,14 +22,18 @@ namespace SixLabors.ImageSharp.Tests // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : IEquatable { - private Tuple commonValues; + private readonly Tuple commonValues; - private Dictionary decoderParameters; + private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) + public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) { Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple(pixelType, filePath, customType); + this.commonValues = new Tuple( + pixelType, + filePath, + customType, + allocatorBufferCapacity); this.decoderParameters = GetDecoderParameters(customDecoder); } @@ -48,8 +52,10 @@ namespace SixLabors.ImageSharp.Tests object value = p.GetValue(customDecoder); data[key] = value; } + type = type.GetTypeInfo().BaseType; } + return data; } @@ -81,11 +87,13 @@ namespace SixLabors.ImageSharp.Tests { return false; } + if (!object.Equals(kv.Value, otherVal)) { return false; } } + return true; } @@ -116,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests public static bool operator !=(Key left, Key right) => !Equals(left, right); } - private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> Cache = new ConcurrentDictionary>(); // Needed for deserialization! // ReSharper disable once UnusedMember.Local @@ -148,9 +156,10 @@ namespace SixLabors.ImageSharp.Tests return this.LoadImage(decoder); } - var key = new Key(this.PixelType, this.FilePath, decoder); + int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); + var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); - Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder)); + Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); return cachedImage.Clone(this.Configuration); } @@ -181,4 +190,4 @@ namespace SixLabors.ImageSharp.Tests return fileProvider?.FilePath; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs new file mode 100644 index 0000000000..199cc43166 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests +{ + public interface ITestImageProvider + { + PixelTypes PixelType { get; } + + ImagingTestCaseUtility Utility { get; } + + string SourceFileOrDescription { get; } + + Configuration Configuration { get; set; } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs deleted file mode 100644 index 5bd53a4c0c..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Provides instances for parametric unit tests. - /// - /// The pixel format of the image - public abstract partial class TestImageProvider - where TPixel : struct, IPixel - { - private class LambdaProvider : TestImageProvider - { - private readonly Func> factoryFunc; - - public LambdaProvider(Func> factoryFunc) - { - this.factoryFunc = factoryFunc; - } - - public override Image GetImage() => this.factoryFunc(); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs new file mode 100644 index 0000000000..45cf570647 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Reflection; +using SixLabors.ImageSharp.PixelFormats; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Provides instances for parametric unit tests. + /// + /// The pixel format of the image + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel + { + private class MemberMethodProvider : TestImageProvider + { + private string declaringTypeName; + private string methodName; + private Func> factoryFunc; + + public MemberMethodProvider() + { + } + + public MemberMethodProvider(string declaringTypeName, string methodName) + { + this.declaringTypeName = declaringTypeName; + this.methodName = methodName; + } + + public override Image GetImage() + { + this.factoryFunc ??= this.GetFactory(); + return this.factoryFunc(); + } + + public override void Serialize(IXunitSerializationInfo info) + { + base.Serialize(info); + + info.AddValue(nameof(this.declaringTypeName), this.declaringTypeName); + info.AddValue(nameof(this.methodName), this.methodName); + } + + public override void Deserialize(IXunitSerializationInfo info) + { + base.Deserialize(info); + + this.methodName = info.GetValue(nameof(this.methodName)); + this.declaringTypeName = info.GetValue(nameof(this.declaringTypeName)); + } + + private Func> GetFactory() + { + var declaringType = Type.GetType(this.declaringTypeName); + MethodInfo m = declaringType.GetMethod(this.methodName); + Type pixelType = typeof(TPixel); + Type imgType = typeof(Image<>).MakeGenericType(pixelType); + Type funcType = typeof(Func<>).MakeGenericType(imgType); + MethodInfo genericMethod = m.MakeGenericMethod(pixelType); + return (Func>)genericMethod.CreateDelegate(funcType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 88b30ce34e..1316473013 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -1,20 +1,19 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. - +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - /// /// Provides instances for parametric unit tests. /// /// The pixel format of the image - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class SolidProvider : BlankProvider { @@ -55,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests Image image = base.GetImage(); Color color = new Rgba32(this.r, this.g, this.b, this.a); - image.Mutate(x => x.Fill(color)); + image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); return image; } @@ -78,4 +77,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 63de4c96f4..c652b32af3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -13,41 +13,35 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public interface ITestImageProvider - { - PixelTypes PixelType { get; } - ImagingTestCaseUtility Utility { get; } - string SourceFileOrDescription { get; } - - Configuration Configuration { get; set; } - } - /// /// Provides instances for parametric unit tests. /// - /// The pixel format of the image - public abstract partial class TestImageProvider : ITestImageProvider - where TPixel : struct, IPixel + /// The pixel format of the image. + public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable + where TPixel : unmanaged, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); - public virtual string SourceFileOrDescription => ""; + public virtual string SourceFileOrDescription => string.Empty; public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); /// - /// Utility instance to provide information about the test image & manage input/output + /// Gets the utility instance to provide information about the test image & manage input/output. /// public ImagingTestCaseUtility Utility { get; private set; } public string TypeName { get; private set; } + public string MethodName { get; private set; } + public string OutputSubfolderName { get; private set; } - public static TestImageProvider BasicTestPattern(int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) + public static TestImageProvider BasicTestPattern( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) => new BasicTestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); public static TestImageProvider TestPattern( @@ -73,10 +67,11 @@ namespace SixLabors.ImageSharp.Tests } public static TestImageProvider Lambda( - Func> factoryFunc, + string declaringTypeName, + string methodName, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new LambdaProvider(factoryFunc).Init(testMethod, pixelTypeOverride); + => new MemberMethodProvider(declaringTypeName, methodName).Init(testMethod, pixelTypeOverride); public static TestImageProvider Solid( int width, @@ -94,6 +89,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns an instance to the test case with the necessary traits. /// + /// A test image. public abstract Image GetImage(); public virtual Image GetImage(IImageDecoder decoder) @@ -104,6 +100,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns an instance to the test case with the necessary traits. /// + /// A test image. public Image GetImage(Action operationsToApply) { Image img = this.GetImage(); @@ -139,6 +136,7 @@ namespace SixLabors.ImageSharp.Tests { this.PixelType = pixelTypeOverride; } + this.TypeName = typeName; this.MethodName = methodName; this.OutputSubfolderName = outputSubfolderName; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 8965458cd0..47b6473296 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -7,28 +7,31 @@ using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { /// /// A test image provider that produces test patterns. /// private class TestPatternProvider : BlankProvider { - static readonly Dictionary> TestImages = new Dictionary>(); + private static readonly Dictionary> TestImages = new Dictionary>(); - private static TPixel[] BlackWhitePixels = new[] { + private static readonly TPixel[] BlackWhitePixels = + { Color.Black.ToPixel(), Color.White.ToPixel() - }; + }; - private static TPixel[] PinkBluePixels = new[] { + private static readonly TPixel[] PinkBluePixels = + { Color.HotPink.ToPixel(), Color.Blue.ToPixel() - }; + }; public TestPatternProvider(int width, int height) : base(width, height) @@ -50,10 +53,11 @@ namespace SixLabors.ImageSharp.Tests { if (!TestImages.ContainsKey(this.SourceFileOrDescription)) { - Image image = new Image(this.Width, this.Height); + var image = new Image(this.Width, this.Height); DrawTestPattern(image); TestImages.Add(this.SourceFileOrDescription, image); } + return TestImages[this.SourceFileOrDescription].Clone(this.Configuration); } } @@ -61,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. /// - /// + /// The image to rdaw on. private static void DrawTestPattern(Image image) { // first lets split the image into 4 quadrants @@ -75,7 +79,6 @@ namespace SixLabors.ImageSharp.Tests /// /// Fills the top right quadrant with alternating solid vertical bars. /// - /// private static void VerticalBars(Buffer2D pixels) { // topLeft @@ -99,6 +102,7 @@ namespace SixLabors.ImageSharp.Tests p++; p = p % PinkBluePixels.Length; } + pixels[x, y] = PinkBluePixels[p]; } } @@ -107,7 +111,6 @@ namespace SixLabors.ImageSharp.Tests /// /// fills the top left quadrant with a black and white checker board. /// - /// private static void BlackWhiteChecker(Buffer2D pixels) { // topLeft @@ -120,21 +123,24 @@ namespace SixLabors.ImageSharp.Tests int p = 0; for (int y = top; y < bottom; y++) { - if (y % stride == 0) + if (y % stride is 0) { p++; p = p % BlackWhitePixels.Length; } + int pstart = p; for (int x = left; x < right; x++) { - if (x % stride == 0) + if (x % stride is 0) { p++; p = p % BlackWhitePixels.Length; } + pixels[x, y] = BlackWhitePixels[p]; } + p = pstart; } } @@ -142,7 +148,6 @@ namespace SixLabors.ImageSharp.Tests /// /// Fills the bottom left quadrant with 3 horizontal bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). /// - /// private static void TransparentGradients(Buffer2D pixels) { // topLeft @@ -152,11 +157,11 @@ namespace SixLabors.ImageSharp.Tests int bottom = pixels.Height; int height = (int)Math.Ceiling(pixels.Height / 6f); - Vector4 red = Rgba32.Red.ToVector4(); // use real color so we can see har it translates in the test pattern - Vector4 green = Rgba32.Green.ToVector4(); // use real color so we can see har it translates in the test pattern - Vector4 blue = Rgba32.Blue.ToVector4(); // use real color so we can see har it translates in the test pattern + var red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - TPixel c = default(TPixel); + var c = default(TPixel); for (int x = left; x < right; x++) { @@ -168,12 +173,14 @@ namespace SixLabors.ImageSharp.Tests { pixels[x, y] = c; } + topBand = topBand + height; c.FromVector4(green); for (int y = topBand; y < topBand + height; y++) { pixels[x, y] = c; } + topBand = topBand + height; c.FromVector4(blue); for (int y = topBand; y < bottom; y++) @@ -187,7 +194,6 @@ namespace SixLabors.ImageSharp.Tests /// Fills the bottom right quadrant with all the colors producible by converting iterating over a uint and unpacking it. /// A better algorithm could be used but it works /// - /// private static void Rainbow(Buffer2D pixels) { int left = pixels.Width / 2; @@ -205,8 +211,9 @@ namespace SixLabors.ImageSharp.Tests for (int y = top; y < bottom; y++) { t.PackedValue += stepsPerPixel; - Vector4 v = t.ToVector4(); - //v.W = (x - left) / (float)left; + var v = t.ToVector4(); + + // v.W = (x - left) / (float)left; c.FromVector4(v); pixels[x, y] = c; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 92cc9f6368..e08dff5255 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,25 +19,26 @@ namespace SixLabors.ImageSharp.Tests public class ImagingTestCaseUtility { /// - /// Name of the TPixel in the owner + /// Gets or sets the name of the TPixel in the owner /// public string PixelTypeName { get; set; } = string.Empty; /// - /// The name of the file which is provided by + /// Gets or sets the name of the file which is provided by /// Or a short string describing the image in the case of a non-file based image provider. /// public string SourceFileOrDescription { get; set; } = string.Empty; /// - /// By default this is the name of the test class, but it's possible to change it + /// Gets or sets the test group name. + /// By default this is the name of the test class, but it's possible to change it. /// public string TestGroupName { get; set; } = string.Empty; public string OutputSubfolderName { get; set; } = string.Empty; /// - /// The name of the test case (by default) + /// Gets or sets the name of the test case (by default). /// public string TestName { get; set; } = string.Empty; @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests string fn = appendSourceFileOrDescription ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) - : ""; + : string.Empty; if (string.IsNullOrWhiteSpace(extension)) { @@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Tests { extension = ".bmp"; } + extension = extension.ToLower(); if (extension[0] != '.') @@ -77,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests fn = '_' + fn; } - string pixName = ""; + string pixName = string.Empty; if (appendPixelTypeToFileName) { @@ -137,8 +139,7 @@ namespace SixLabors.ImageSharp.Tests detailsString = string.Join( "_", properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) - .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}")) - ); + .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); } } @@ -152,12 +153,13 @@ namespace SixLabors.ImageSharp.Tests /// /// Encodes image by the format matching the required extension, than saves it to the recommended output file. /// - /// The image instance - /// The requested extension - /// Optional encoder - /// A value indicating whether to append the pixel type to the test output file name + /// The image instance. + /// The requested extension. + /// Optional encoder. + /// Additional information to append to the test output file name. + /// A value indicating whether to append the pixel type to the test output file name. /// A boolean indicating whether to append to the test output file name. - /// Additional information to append to the test output file name + /// The path to the saved image file. public string SaveTestOutputFile( Image image, string extension = null, @@ -189,7 +191,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) { - string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); + string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); if (!Directory.Exists(baseDir)) { @@ -209,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests IImageEncoder encoder = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); @@ -241,8 +243,7 @@ namespace SixLabors.ImageSharp.Tests bool appendSourceFileOrDescription) { return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription) - ); + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); } public string[] GetReferenceOutputFileNamesMultiFrame( @@ -275,10 +276,10 @@ namespace SixLabors.ImageSharp.Tests } public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) - where TPixel : struct, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); + where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = img[x, y]; Rgba64 rgbaPixel = default; @@ -325,4 +326,4 @@ namespace SixLabors.ImageSharp.Tests img[x, y] = pixel; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 61bd7cd9fb..b01ece7ba3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Value indicating whether printing is enabled. /// - protected bool EnablePrinting = true; + protected bool enablePrinting = true; /// /// Measures and prints the execution time of an , executed multiple times. @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests /// The name of the operation to print to the output public void Measure(int times, Action action, [CallerMemberName] string operationName = null) { - if (this.EnablePrinting) + if (this.enablePrinting) { this.Output?.WriteLine($"{operationName} X {times} ..."); } @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests } sw.Stop(); - if (this.EnablePrinting) + if (this.enablePrinting) { this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); } diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index 78431f31aa..9d3427a069 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,9 +12,10 @@ namespace SixLabors.ImageSharp.Tests [Flags] public enum PixelTypes { +#pragma warning disable SA1602 // Enumeration items should be documented Undefined = 0, - Alpha8 = 1 << 0, + A8 = 1 << 0, Argb32 = 1 << 1, @@ -60,11 +61,19 @@ namespace SixLabors.ImageSharp.Tests Bgra5551 = 1 << 22, - Gray8 = 1 << 23, + L8 = 1 << 23, + + L16 = 1 << 24, + + La16 = 1 << 25, + + La32 = 1 << 26, // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR All = 30 + +#pragma warning restore SA1602 // Enumeration items should be documented } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index e81714ddc4..4708d70b0d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -3,12 +3,14 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ImageMagick; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -17,47 +19,66 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); + private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba32Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 4); + } + } + + private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba64Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); + } + } + public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var magickImage = new MagickImage(stream)) + using var magickImage = new MagickImage(stream); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + MemoryGroup resultPixels = result.GetRootFramePixelBuffer().FastMemoryGroup; + + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) { - var result = new Image(configuration, magickImage.Width, magickImage.Height); - Span resultPixels = result.GetPixelSpan(); + if (magickImage.Depth == 8) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) + FromRgba32Bytes(configuration, data, resultPixels); + } + else if (magickImage.Depth == 16) { - if (magickImage.Depth == 8) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels, - resultPixels.Length); - } - else if (magickImage.Depth == 16) - { - ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); - Span bytes = MemoryMarshal.Cast(data.AsSpan()); - - PixelOperations.Instance.FromRgba64Bytes( - configuration, - bytes, - resultPixels, - resultPixels.Length); - } - else - { - throw new InvalidOperationException(); - } + ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); + Span bytes = MemoryMarshal.Cast(data.AsSpan()); + FromRgba64Bytes(configuration, bytes, resultPixels); + } + else + { + throw new InvalidOperationException(); } - - return result; } + + return result; } - + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 79c19f2be7..eb6f5e8c53 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs /// The input bitmap. /// Thrown if the image pixel format is not of type internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int w = bmp.Width; int h = bmp.Height; - var fullRect = new Rectangle(0, 0, w, h); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); if (bmp.PixelFormat != PixelFormat.Format32bppArgb) { @@ -83,12 +83,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs /// The input bitmap. /// Thrown if the image pixel format is not of type internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int w = bmp.Width; int h = bmp.Height; - var fullRect = new Rectangle(0, 0, w, h); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); if (bmp.PixelFormat != PixelFormat.Format24bppRgb) { @@ -128,18 +128,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { bmp.UnlockBits(data); } + return image; } internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Configuration configuration = image.GetConfiguration(); int w = image.Width; int h = image.Height; var resultBitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb); - var fullRect = new Rectangle(0, 0, w, h); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); try { @@ -171,4 +172,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return resultBitmap; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 2de3c03aaf..254112339c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) { @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } } - + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index 46dae17a11..563fe2cb3d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp); public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 7d06847223..d49c34efd9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests @@ -53,8 +54,8 @@ namespace SixLabors.ImageSharp.Tests { var cfg = new Configuration( new JpegConfigurationModule(), - new GifConfigurationModule() - ); + new GifConfigurationModule(), + new TgaConfigurationModule()); // Magick codecs should work on all platforms IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder(); @@ -75,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests return cfg; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index a5a3e332c7..4152d3bc66 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests { @@ -32,12 +34,18 @@ namespace SixLabors.ImageSharp.Tests private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + static TestEnvironment() + { + PrepareRemoteExecutor(); + } + /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// internal static string NetCoreVersion => NetCoreVersionLazy.Value; // ReSharper disable once InconsistentNaming + /// /// Gets a value indicating whether test execution runs on CI. /// @@ -45,13 +53,12 @@ namespace SixLabors.ImageSharp.Tests internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; + private static readonly FileInfo TestAssemblyFile = + new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); + private static string GetSolutionDirectoryFullPathImpl() { - string assemblyLocation = typeof(TestEnvironment).GetTypeInfo().Assembly.Location; - - var assemblyFile = new FileInfo(assemblyLocation); - - DirectoryInfo directory = assemblyFile.Directory; + DirectoryInfo directory = TestAssemblyFile.Directory; while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) { @@ -62,20 +69,20 @@ namespace SixLabors.ImageSharp.Tests catch (Exception ex) { throw new Exception( - $"Unable to find ImageSharp solution directory from {assemblyLocation} because of {ex.GetType().Name}!", + $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", ex); } if (directory == null) { - throw new Exception($"Unable to find ImageSharp solution directory from {assemblyLocation}!"); + throw new Exception($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); } } return directory.FullName; } - private static string GetFullPath(string relativePath) => + private static string GetFullPath(string relativePath) => Path.Combine(SolutionDirectoryFullPath, relativePath) .Replace('\\', Path.DirectorySeparatorChar); @@ -83,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests /// Gets the correct full path to the Input Images directory. /// internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); - + /// /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) /// @@ -100,13 +107,22 @@ namespace SixLabors.ImageSharp.Tests actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - + internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); internal static bool Is64BitProcess => IntPtr.Size == 8; + internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + + /// + /// A dummy operation to enforce the execution of the static constructor. + /// + internal static void EnsureSharedInitializersDone() + { + } + /// /// Creates the image output directory. /// @@ -132,6 +148,109 @@ namespace SixLabors.ImageSharp.Tests return path; } + /// + /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, + /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe + /// with the help of CorFlags.exe found in Windows SDK. + /// + private static void PrepareRemoteExecutor() + { + if (!IsFramework) + { + return; + } + + string remoteExecutorConfigPath = + Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + + if (File.Exists(remoteExecutorConfigPath)) + { + // Already initialized + return; + } + + string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; + File.Copy(testProjectConfigPath, remoteExecutorConfigPath); + + if (Is64BitProcess) + { + return; + } + + EnsureRemoteExecutorIs32Bit(); + } + + /// + /// Locate and run CorFlags.exe /32Bit+ + /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool + /// + private static void EnsureRemoteExecutorIs32Bit() + { + string windowsSdksDir = Path.Combine( + Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), + "Microsoft SDKs", + "Windows"); + + FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); + + string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); + + string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + + if (File.Exists(remoteExecutorTmpPath)) + { + // Already initialized + return; + } + + File.Copy(remoteExecutorPath, remoteExecutorTmpPath); + + string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; + + var si = new ProcessStartInfo() + { + FileName = corFlagsFile.FullName, + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using var proc = Process.Start(si); + proc.WaitForExit(); + string standardOutput = proc.StandardOutput.ReadToEnd(); + string standardError = proc.StandardError.ReadToEnd(); + + if (proc.ExitCode != 0) + { + throw new Exception( + $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); + } + + File.Delete(remoteExecutorPath); + File.Copy(remoteExecutorTmpPath, remoteExecutorPath); + + static FileInfo Find(DirectoryInfo root, string name) + { + FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); + if (fi != null) + { + return fi; + } + + foreach (DirectoryInfo dir in root.EnumerateDirectories()) + { + fi = Find(dir, name); + if (fi != null) + { + return fi; + } + } + + return null; + } + } + /// /// Solution borrowed from: /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 @@ -142,8 +261,11 @@ namespace SixLabors.ImageSharp.Tests string[] assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + { return assemblyPath[netCoreAppIndex + 1]; - return ""; + } + + return string.Empty; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 95f7f81fda..fbdb512dea 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -5,16 +5,14 @@ using System; using System.Collections.Generic; using System.IO; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; @@ -25,7 +23,6 @@ namespace SixLabors.ImageSharp.Tests /// /// TODO: Consider adding this private processor to the library /// - /// public static void MakeOpaque(this IImageProcessingContext ctx) => ctx.ApplyProcessor(new MakeOpaqueProcessor()); @@ -50,13 +47,14 @@ namespace SixLabors.ImageSharp.Tests /// /// Saves the image only when not running in the CI server. /// - /// The image - /// The image provider + /// The image. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension + /// The extension. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. /// Custom encoder to use. + /// The input image. public static Image DebugSave( this Image image, ITestImageProvider provider, @@ -126,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (TestEnvironment.RunsOnCI) { @@ -150,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareToReferenceOutput( provider, @@ -165,15 +163,15 @@ namespace SixLabors.ImageSharp.Tests /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . /// - /// The pixel format - /// The image - /// The image provider + /// The pixel format. + /// The image which should be compared to the reference image. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. - /// + /// The image. public static Image CompareToReferenceOutput( this Image image, ITestImageProvider provider, @@ -182,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return CompareToReferenceOutput( image, @@ -203,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareToReferenceOutput( comparer, @@ -218,17 +216,17 @@ namespace SixLabors.ImageSharp.Tests /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . /// - /// The pixel format - /// The image - /// The to use - /// The image provider + /// The pixel format. + /// The image which should be compared to the reference output. + /// The to use. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. /// A custom decoder. - /// + /// The image. public static Image CompareToReferenceOutput( this Image image, ImageComparer comparer, @@ -239,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true, IImageDecoder decoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = GetReferenceOutputImage( provider, @@ -264,7 +262,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareFirstFrameToReferenceOutput( comparer, @@ -285,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) using (Image referenceImage = GetReferenceOutputImage( @@ -312,7 +310,7 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = GetReferenceOutputImageMultiFrame( provider, @@ -327,13 +325,14 @@ namespace SixLabors.ImageSharp.Tests return image; } - public static Image GetReferenceOutputImage(this ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageDecoder decoder = null) - where TPixel : struct, IPixel + public static Image GetReferenceOutputImage( + this ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true, + IImageDecoder decoder = null) + where TPixel : unmanaged, IPixel { string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( extension, @@ -343,20 +342,21 @@ namespace SixLabors.ImageSharp.Tests if (!File.Exists(referenceOutputFile)) { - throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); } - decoder = decoder ?? TestEnvironment.GetReferenceDecoder(referenceOutputFile); + decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); return Image.Load(referenceOutputFile, decoder); } - public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, - int frameCount, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + public static Image GetReferenceOutputImageMultiFrame( + this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel { string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( frameCount, @@ -389,7 +389,7 @@ namespace SixLabors.ImageSharp.Tests fi.Dispose(); } - // remove the initial empty frame: + // Remove the initial empty frame: result.Frames.RemoveFrame(0); return result; } @@ -401,7 +401,7 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = provider.GetReferenceOutputImage( testOutputDetails, @@ -415,10 +415,9 @@ namespace SixLabors.ImageSharp.Tests public static Image ComparePixelBufferTo( this Image image, Span expectedPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Span actualPixels = image.GetPixelSpan(); - + Assert.True(image.TryGetSinglePixelSpan(out Span actualPixels)); CompareBuffers(expectedPixels, actualPixels); return image; @@ -441,8 +440,10 @@ namespace SixLabors.ImageSharp.Tests /// /// All pixels in all frames should be exactly equal to 'expectedPixel'. /// + /// The pixel type of the image. + /// The image. public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (ImageFrame imageFrame in image.Frames) { @@ -455,8 +456,10 @@ namespace SixLabors.ImageSharp.Tests /// /// All pixels in all frames should be exactly equal to 'expectedPixelColor.ToPixel()'. /// + /// The pixel type of the image. + /// The image. public static Image ComparePixelBufferTo(this Image image, Color expectedPixelColor) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (ImageFrame imageFrame in image.Frames) { @@ -469,10 +472,12 @@ namespace SixLabors.ImageSharp.Tests /// /// All pixels in the frame should be exactly equal to 'expectedPixel'. /// + /// The pixel type of the image. + /// The image. public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Span actualPixels = imageFrame.GetPixelSpan(); + Assert.True(imageFrame.TryGetSinglePixelSpan(out Span actualPixels)); for (int i = 0; i < actualPixels.Length; i++) { @@ -485,10 +490,9 @@ namespace SixLabors.ImageSharp.Tests public static ImageFrame ComparePixelBufferTo( this ImageFrame image, Span expectedPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Span actual = image.GetPixelSpan(); - + Assert.True(image.TryGetSinglePixelSpan(out Span actual)); Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) @@ -503,7 +507,7 @@ namespace SixLabors.ImageSharp.Tests this Image image, ITestImageProvider provider, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); } @@ -513,7 +517,7 @@ namespace SixLabors.ImageSharp.Tests ITestImageProvider provider, ImageComparer comparer, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestImageProvider.GetFilePathOrNull(provider); if (path == null) @@ -546,7 +550,7 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -558,7 +562,8 @@ namespace SixLabors.ImageSharp.Tests appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - image.CompareToReferenceOutput(comparer, + image.CompareToReferenceOutput( + comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, @@ -578,7 +583,7 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation( ImageComparer.Tolerant(), @@ -600,7 +605,7 @@ namespace SixLabors.ImageSharp.Tests Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation( comparer, @@ -621,7 +626,7 @@ namespace SixLabors.ImageSharp.Tests Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); } @@ -640,7 +645,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, string referenceImageExtension = null, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string actualOutputFile = provider.Utility.SaveTestOutputFile( image, @@ -649,22 +654,29 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, appendPixelTypeToFileName); - referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(actualOutputFile); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + using (var encodedImage = Image.Load(actualOutputFile, referenceDecoder)) { ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(actualImage, image); + comparer.VerifySimilarity(encodedImage, image); } } + internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( + this TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; + return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); + } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); - Span pixels = image.Frames.RootFrame.GetPixelSpan(); - - Span bufferSpan = buffer.GetSpan(); + Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span pixels)); + Span bufferSpan = buffer.GetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) { @@ -678,41 +690,87 @@ namespace SixLabors.ImageSharp.Tests private class MakeOpaqueProcessor : IImageProcessor { - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new MakeOpaqueProcessor(source, sourceRectangle); + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new MakeOpaqueProcessor(configuration, source, sourceRectangle); } private class MakeOpaqueProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - public MakeOpaqueProcessor(Image source, Rectangle sourceRectangle) - : base(source, sourceRectangle) + public MakeOpaqueProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - } protected override void OnFrameApply(ImageFrame source) { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelHelper.IterateRowsWithTempBuffer(sourceRectangle, configuration, - (rows, temp) => + + var operation = new RowOperation(configuration, sourceRectangle, source); + + ParallelRowIterator.IterateRowIntervals( + configuration, + sourceRectangle, + in operation); + } + + private readonly struct RowOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly ImageFrame source; + + public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.source = source; + } + + public void Invoke(in RowInterval rows, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + for (int i = 0; i < span.Length; i++) { - Span tempSpan = temp.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width); - PixelOperations.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); - for (int i = 0; i < tempSpan.Length; i++) - { - ref Vector4 v = ref tempSpan[i]; - v.W = 1F; - } - PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); - } - }); + ref Vector4 v = ref span[i]; + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + } + } } } } + + internal class AllocatorBufferCapacityConfigurator + { + private readonly ArrayPoolMemoryAllocator allocator; + private readonly int pixelSizeInBytes; + + public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + { + this.allocator = allocator; + this.pixelSizeInBytes = pixelSizeInBytes; + } + + public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; + + public void InPixels(int totalPixels) => this.InBytes(totalPixels * this.pixelSizeInBytes); + + /// + /// Set the maximum buffer capacity to bytesSqrt^2 bytes. + /// + public void InBytesSqrt(int bytesSqrt) => this.InBytes(bytesSqrt * bytesSqrt); + + /// + /// Set the maximum buffer capacity to pixelsSqrt^2 x sizeof(TPixel) bytes. + /// + public void InPixelsSqrt(int pixelsSqrt) => this.InPixels(pixelsSqrt * pixelsSqrt); + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index e1209a0c6a..dd928cb755 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -1,16 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; -using SixLabors.Memory; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { - private List allocationLog = new List(); + private readonly List allocationLog = new List(); + private readonly List returnLog = new List(); public TestMemoryAllocator(byte dirtyValue = 42) { @@ -18,29 +22,35 @@ namespace SixLabors.ImageSharp.Tests.Memory } /// - /// The value to initialize the result buffer with, with non-clean options () + /// Gets the value to initialize the result buffer with, with non-clean options () /// public byte DirtyValue { get; } - public IList AllocationLog => this.allocationLog; + public int BufferCapacityInBytes { get; set; } = int.MaxValue; + + public IReadOnlyList AllocationLog => this.allocationLog; + + public IReadOnlyList ReturnLog => this.returnLog; + + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length); + return new BasicArrayBuffer(array, length, this); } public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) { byte[] array = this.AllocateArray(length, options); - return new ManagedByteBuffer(array); + return new ManagedByteBuffer(array, this); } private T[] AllocateArray(int length, AllocationOptions options) where T : struct { - this.allocationLog.Add(AllocationRequest.Create(options, length)); var array = new T[length + 42]; + this.allocationLog.Add(AllocationRequest.Create(options, length, array)); if (options == AllocationOptions.None) { @@ -51,34 +61,54 @@ namespace SixLabors.ImageSharp.Tests.Memory return array; } + private void Return(BasicArrayBuffer buffer) + where T : struct + { + this.returnLog.Add(new ReturnRequest(buffer.Array.GetHashCode())); + } + public struct AllocationRequest { - private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes) + private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes, int hashCodeOfBuffer) { this.ElementType = elementType; this.AllocationOptions = allocationOptions; this.Length = length; this.LengthInBytes = lengthInBytes; + this.HashCodeOfBuffer = hashCodeOfBuffer; if (elementType == typeof(Vector4)) { - } } - public static AllocationRequest Create(AllocationOptions allocationOptions, int length) + public static AllocationRequest Create(AllocationOptions allocationOptions, int length, T[] buffer) { Type type = typeof(T); int elementSize = Marshal.SizeOf(type); - return new AllocationRequest(type, allocationOptions, length, length * elementSize); + return new AllocationRequest(type, allocationOptions, length, length * elementSize, buffer.GetHashCode()); } public Type ElementType { get; } + public AllocationOptions AllocationOptions { get; } + public int Length { get; } + public int LengthInBytes { get; } + + public int HashCodeOfBuffer { get; } } + public struct ReturnRequest + { + public ReturnRequest(int hashCodeOfBuffer) + { + this.HashCodeOfBuffer = hashCodeOfBuffer; + } + + public int HashCodeOfBuffer { get; } + } /// /// Wraps an array as an instance. @@ -86,36 +116,29 @@ namespace SixLabors.ImageSharp.Tests.Memory private class BasicArrayBuffer : MemoryManager where T : struct { + private readonly TestMemoryAllocator allocator; private GCHandle pinHandle; - /// - /// Initializes a new instance of the class - /// - /// The array - /// The length of the buffer - public BasicArrayBuffer(T[] array, int length) + public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) { + this.allocator = allocator; DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); this.Array = array; this.Length = length; } - /// - /// Initializes a new instance of the class - /// - /// The array - public BasicArrayBuffer(T[] array) - : this(array, array.Length) + public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) + : this(array, array.Length, allocator) { } /// - /// Gets the array + /// Gets the array. /// public T[] Array { get; } /// - /// Gets the length + /// Gets the length. /// public int Length { get; } @@ -141,15 +164,19 @@ namespace SixLabors.ImageSharp.Tests.Memory /// protected override void Dispose(bool disposing) { + if (disposing) + { + this.allocator.Return(this); + } } } private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer { - public ManagedByteBuffer(byte[] array) - : base(array) + public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) + : base(array, allocator) { } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs index 9274e5727c..3fd5f6e37b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs @@ -1,9 +1,12 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Buffers; namespace SixLabors.ImageSharp.Tests { - class TestMemoryManager : MemoryManager + public class TestMemoryManager : MemoryManager where T : struct { public TestMemoryManager(T[] pixelArray) @@ -43,4 +46,4 @@ namespace SixLabors.ImageSharp.Tests this.PixelArray = null; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index 1e1a45f074..ba146b9e4b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,7 +10,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.TestUtilities { public class TestPixel : IXunitSerializable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public TestPixel() { @@ -25,25 +25,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities } public float Red { get; set; } + public float Green { get; set; } + public float Blue { get; set; } - public float Alpha { get; set; } - public static implicit operator TPixel(TestPixel d) - { - return d?.AsPixel() ?? default(TPixel); - } + public float Alpha { get; set; } public TPixel AsPixel() { - TPixel pix = default(TPixel); + var pix = default(TPixel); pix.FromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); return pix; } internal Span AsSpan() { - return new Span(new[] { AsPixel() }); + return new Span(new[] { this.AsPixel() }); } public void Deserialize(IXunitSerializationInfo info) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index b56ce05171..8af20a94fd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,13 +6,15 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; +using Xunit; namespace SixLabors.ImageSharp.Tests { @@ -39,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; // Add PixelFormat types - string nameSpace = typeof(Alpha8).FullName; - nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(Alpha8).Name.Length - 1); + string nameSpace = typeof(A8).FullName; + nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(A8).Name.Length - 1); foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) { string typeName = $"{nameSpace}.{pt}"; @@ -53,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (a.Width != b.Width || a.Height != b.Height) { @@ -105,8 +107,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns the enumerations for the given type. /// - /// - /// + /// The pixel type. public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) @@ -129,6 +130,7 @@ namespace SixLabors.ImageSharp.Tests result[pt] = pt.GetClrType(); } } + return result; } @@ -148,9 +150,31 @@ namespace SixLabors.ImageSharp.Tests } internal static TPixel GetPixelOfNamedColor(string colorName) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => GetColorByName(colorName).ToPixel(); + internal static void RunBufferCapacityLimitProcessorTest( + this TestImageProvider provider, + int bufferCapacityInPixelRows, + Action process, + object testOutputDetails = null, + ImageComparer comparer = null) + where TPixel : unmanaged, IPixel + { + comparer ??= ImageComparer.Exact; + using Image expected = provider.GetImage(); + int width = expected.Width; + expected.Mutate(process); + + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); + provider.Configuration.MemoryAllocator = allocator; + allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); + + using Image actual = provider.GetImage(); + actual.Mutate(process); + comparer.VerifySimilarity(expected, actual); + } + /// /// Utility for testing image processor extension methods: /// 1. Run a processor defined by 'process' @@ -161,8 +185,8 @@ namespace SixLabors.ImageSharp.Tests /// The image processing method to test. (As a delegate) /// The value to append to the test output. /// The custom image comparer to use - /// - /// + /// If true, the pixel type will by appended to the output file. + /// A boolean indicating whether to append to the test output file name. internal static void RunValidatingProcessorTest( this TestImageProvider provider, Action process, @@ -170,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests ImageComparer comparer = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -206,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests ImageComparer comparer = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -216,9 +240,7 @@ namespace SixLabors.ImageSharp.Tests using (Image image = provider.GetImage()) { FormattableString testOutputDetails = $""; - image.Mutate( - ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); } - ); + image.Mutate(ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); }); image.DebugSave( provider, @@ -247,7 +269,7 @@ namespace SixLabors.ImageSharp.Tests string useReferenceOutputFrom = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -256,7 +278,8 @@ namespace SixLabors.ImageSharp.Tests using (Image image0 = provider.GetImage()) { - var mmg = TestMemoryManager.CreateAsCopyOf(image0.GetPixelSpan()); + Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { @@ -297,8 +320,9 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider, Action process, object testOutputDetails = null, - ImageComparer comparer = null) - where TPixel : struct, IPixel + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -309,8 +333,8 @@ namespace SixLabors.ImageSharp.Tests { var bounds = new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2); image.Mutate(x => process(x, bounds)); - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails); + image.DebugSave(provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); } } @@ -321,7 +345,7 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider, Action process, object testOutputDetails = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -344,6 +368,18 @@ namespace SixLabors.ImageSharp.Tests return (IResampler)property.GetValue(null); } + public static IDither GetDither(string name) + { + PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No dither named '{name}"); + } + + return (IDither)property.GetValue(null); + } + public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) { return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) @@ -352,4 +388,4 @@ namespace SixLabors.ImageSharp.Tests .ToArray(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs index 990258e0c2..8677184e32 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -21,8 +21,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities } public float X { get; set; } + public float Y { get; set; } + public float Z { get; set; } + public float W { get; set; } public static implicit operator Vector4(TestVector4 d) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs new file mode 100644 index 0000000000..5039f0154f --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests +{ + public class BasicSerializerTests + { + internal class BaseObj : IXunitSerializable + { + public double Length { get; set; } + + public string Name { get; set; } + + public int Lives { get; set; } + + public virtual void Deserialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(this.Length), this.Length); + info.AddValue(nameof(this.Name), this.Name); + info.AddValue(nameof(this.Lives), this.Lives); + } + + public virtual void Serialize(IXunitSerializationInfo info) + { + this.Length = info.GetValue(nameof(this.Length)); + this.Name = info.GetValue(nameof(this.Name)); + this.Lives = info.GetValue(nameof(this.Lives)); + } + } + + internal class DerivedObj : BaseObj + { + public double Strength { get; set; } + + public override void Deserialize(IXunitSerializationInfo info) + { + this.Strength = info.GetValue(nameof(this.Strength)); + base.Deserialize(info); + } + + public override void Serialize(IXunitSerializationInfo info) + { + base.Serialize(info); + info.AddValue(nameof(this.Strength), this.Strength); + } + } + + [Fact] + public void SerializeDeserialize_ShouldPreserveValues() + { + var obj = new DerivedObj() { Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8 }; + + string str = BasicSerializer.Serialize(obj); + BaseObj mirrorBase = BasicSerializer.Deserialize(str); + + DerivedObj mirror = Assert.IsType(mirrorBase); + Assert.Equal(obj.Length, mirror.Length); + Assert.Equal(obj.Name, mirror.Name); + Assert.Equal(obj.Lives, mirror.Lives); + Assert.Equal(obj.Strength, mirror.Strength); + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs index 061d42b0ac..f411e9b08e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.Equal("Foo", provider.Utility.OutputSubfolderName); } @@ -20,10 +23,10 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string expected = $"{Path.DirectorySeparatorChar}Foo{Path.DirectorySeparatorChar}"; Assert.Contains(expected, provider.Utility.GetTestOutputDir()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 61db992988..b977ca022c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Collections.Generic; using System.Linq; @@ -6,7 +9,6 @@ using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -29,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests TestImageProvider provider, float imageThreshold, int pixelThreshold) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -61,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -84,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -104,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 99, 100)] [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 99)] public void VerifySimilarity_ThrowsOnSizeMismatch(TestImageProvider provider, int w, int h) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -121,11 +123,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -141,11 +142,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -179,4 +179,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index b60439b488..6083b1faef 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { using SixLabors.ImageSharp.PixelFormats; @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Splash)] [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Indexed)] public void MagickDecode_8BitDepthImage_IsEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(testImage); @@ -58,9 +58,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48Bpp)] [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppInterlaced)] [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppTrans)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Gray16Bit)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.L16Bit)] public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(testImage); @@ -84,4 +84,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index b0d3b8c7e8..9f9ea44f9f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkMagickPngDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Png"); } @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Png"); } @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkMagickBmpDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Bmp"); } @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp"); } @@ -81,7 +81,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests private void BenchmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) { var measure = new MeasureFixture(this.Output); - measure.Measure(times, + measure.Measure( + times, () => { foreach (string testFile in testFiles) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs index 4a02d280e5..4c0d5758fe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } private static string SavePng(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image sourceImage = provider.GetImage()) { @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (TestEnvironment.IsLinux) { @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = SavePng(provider, PngColorType.Rgb); @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); using (var image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void SaveWithReferenceEncoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 096f78299b..160b1fe407 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.IO; -using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -14,8 +13,8 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class TestEnvironmentTests @@ -33,29 +32,6 @@ namespace SixLabors.ImageSharp.Tests Assert.True(Directory.Exists(path)); } - /// - /// We need this test to make sure that the netcoreapp2.1 test execution actually covers the netcoreapp2.1 build configuration of ImageSharp. - /// - [Fact] - public void ImageSharpAssemblyUnderTest_MatchesExpectedTargetFramework() - { - this.Output.WriteLine("NetCoreVersion: " + TestEnvironment.NetCoreVersion); - this.Output.WriteLine("ImageSharpBuiltAgainst: " + TestHelpers.ImageSharpBuiltAgainst); - - if (string.IsNullOrEmpty(TestEnvironment.NetCoreVersion)) - { - this.Output.WriteLine("Not running under .NET Core!"); - } - else if (TestEnvironment.NetCoreVersion.StartsWith("2.1")) - { - Assert.Equal("netcoreapp2.1", TestHelpers.ImageSharpBuiltAgainst); - } - else - { - Assert.Equal("netstandard2.0", TestHelpers.ImageSharpBuiltAgainst); - } - } - [Fact] public void SolutionDirectoryFullPath() { @@ -91,7 +67,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); @@ -104,7 +83,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); @@ -117,7 +99,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) { - if (!TestEnvironment.IsLinux) return; + if (!TestEnvironment.IsLinux) + { + return; + } IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); @@ -130,7 +115,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) { - if (!TestEnvironment.IsLinux) return; + if (!TestEnvironment.IsLinux) + { + return; + } IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs index 6a1582828a..30f7f1f161 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using Moq; @@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -27,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_DoNotAppendPixelType( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -51,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -62,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void CompareToOriginal_WhenSimilar(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -76,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -92,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -103,4 +106,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 738465a6ed..498f3edca5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -14,7 +14,6 @@ using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests { public class TestImageProviderTests @@ -40,17 +39,16 @@ namespace SixLabors.ImageSharp.Tests /// /// Need to us to create instance of when pixelType is StandardImageClass /// - /// - /// - /// + /// The pixel type of the image. + /// A test image. public static Image CreateTestImage() - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => new Image(3, 3); [Theory] [MemberData(nameof(BasicData))] public void Blank_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); @@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [MemberData(nameof(FileData))] public void File_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); @@ -74,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -104,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); @@ -132,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -165,21 +163,21 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => Assert.Empty(provider.Utility.OutputSubfolderName); [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] - [WithBlankImages(1, 1, PixelTypes.Alpha8, PixelTypes.Alpha8)] + [WithBlankImages(1, 1, PixelTypes.A8, PixelTypes.A8)] [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => Assert.Equal(expected, provider.PixelType); [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void SaveTestOutputFileMultiFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] public void Use_WithBasicTestPatternImages(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -212,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests public void Use_WithBlankImagesAttribute_WithAllPixelTypes( TestImageProvider provider, string message) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); @@ -224,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); @@ -237,7 +235,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image img = provider.GetImage()) @@ -254,7 +252,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { EnsureCustomConfigurationIsApplied(provider); } @@ -262,7 +260,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithFileCollection(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image image = provider.GetImage()) @@ -274,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); Assert.Equal(3, img.Width); @@ -287,7 +285,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); Assert.Equal(10, img.Width); @@ -312,7 +310,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -323,13 +321,13 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { EnsureCustomConfigurationIsApplied(provider); } private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (provider.GetImage()) { @@ -348,8 +346,7 @@ namespace SixLabors.ImageSharp.Tests private class TestDecoder : IImageDecoder { // Couldn't make xUnit happy without this hackery: - - private static readonly ConcurrentDictionary invocationCounts = + private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); private static readonly object Monitor = new object(); @@ -365,18 +362,18 @@ namespace SixLabors.ImageSharp.Tests } public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - invocationCounts[this.callerName]++; + InvocationCounts[this.callerName]++; return new Image(42, 42); } - internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal void InitCaller(string name) { this.callerName = name; - invocationCounts[name] = 0; + InvocationCounts[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); @@ -384,7 +381,7 @@ namespace SixLabors.ImageSharp.Tests private class TestDecoderWithParameters : IImageDecoder { - private static readonly ConcurrentDictionary invocationCounts = + private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); private static readonly object Monitor = new object(); @@ -404,18 +401,18 @@ namespace SixLabors.ImageSharp.Tests } public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - invocationCounts[this.callerName]++; + InvocationCounts[this.callerName]++; return new Image(42, 42); } - internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal void InitCaller(string name) { this.callerName = name; - invocationCounts[name] = 0; + InvocationCounts[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 301d0cebe6..821370b7a3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests private ITestOutputHelper Output { get; } public static Image CreateTestImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var image = new Image(10, 10); @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, true)] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, false)] public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image a = provider.GetImage(); Image b = provider.GetImage(x => x.OilPaint(3, 2)); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, true)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, false)] public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image a = provider.GetImage(); Image b = provider.GetImage(); @@ -93,19 +93,18 @@ namespace SixLabors.ImageSharp.Tests IEnumerable> pixelTypesExp) { Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); - } [Fact] public void ExpandAllTypes_1() { - PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; + PixelTypes pixelTypes = PixelTypes.A8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; IEnumerable> expanded = pixelTypes.ExpandAllTypes(); Assert.Equal(4, expanded.Count()); - AssertContainsPixelType(PixelTypes.Alpha8, expanded); + AssertContainsPixelType(PixelTypes.A8, expanded); AssertContainsPixelType(PixelTypes.Bgr565, expanded); AssertContainsPixelType(PixelTypes.HalfVector2, expanded); AssertContainsPixelType(PixelTypes.Rgba32, expanded); diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs index 9612882b3d..ba82eb1ac8 100644 --- a/tests/ImageSharp.Tests/VectorAssert.cs +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests public static class VectorAssert { public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Equal(expected.ToVector4(), actual.ToVector4(), precision); } @@ -48,25 +48,23 @@ namespace SixLabors.ImageSharp.Tests public bool Equals(Vector2 x, Vector2 y) { - return Equals(x.X, y.X) && - Equals(x.Y, y.Y); - + return this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y); } + public bool Equals(Vector3 x, Vector3 y) { - return Equals(x.X, y.X) && - Equals(x.Y, y.Y) && - Equals(x.Z, y.Z); - + return this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y) && + this.Equals(x.Z, y.Z); } public bool Equals(Vector4 x, Vector4 y) { - return Equals(x.W, y.W) && - Equals(x.X, y.X) && - Equals(x.Y, y.Y) && - Equals(x.Z, y.Z); - + return this.Equals(x.W, y.W) && + this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y) && + this.Equals(x.Z, y.Z); } public bool Equals(float x, float y) @@ -78,10 +76,12 @@ namespace SixLabors.ImageSharp.Tests { return obj.GetHashCode(); } + public int GetHashCode(Vector3 obj) { return obj.GetHashCode(); } + public int GetHashCode(Vector2 obj) { return obj.GetHashCode(); diff --git a/tests/Images/External b/tests/Images/External index 99a2bc523c..0d1f91e2fe 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 99a2bc523cd4eb00e37af20d1b2088fa11564c57 +Subproject commit 0d1f91e2fe1491f6dc2c137a8ea20460fde4404c diff --git a/tests/Images/Input/Gif/image-zero-height.gif b/tests/Images/Input/Gif/image-zero-height.gif new file mode 100644 index 0000000000..f4f70ab6a4 --- /dev/null +++ b/tests/Images/Input/Gif/image-zero-height.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37248eeb127e43bb002f621409cb6dabaa6b58a62612d26009722c4ae7c83dd6 +size 30 diff --git a/tests/Images/Input/Gif/image-zero-size.gif b/tests/Images/Input/Gif/image-zero-size.gif new file mode 100644 index 0000000000..c2bccffc49 --- /dev/null +++ b/tests/Images/Input/Gif/image-zero-size.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:518aa6f50b003b76e8b65e798d2a37b6dad7dade96d0a7db73da88eec07efe0e +size 30 diff --git a/tests/Images/Input/Gif/image-zero-width.gif b/tests/Images/Input/Gif/image-zero-width.gif new file mode 100644 index 0000000000..642be49ad4 --- /dev/null +++ b/tests/Images/Input/Gif/image-zero-width.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cde31fe4bcc863f70f66c5a57d62647b11512920328fc5658399ef566ebebef +size 30 diff --git a/tests/Images/Input/Gif/max-height.gif b/tests/Images/Input/Gif/max-height.gif new file mode 100644 index 0000000000..fcec4bd936 --- /dev/null +++ b/tests/Images/Input/Gif/max-height.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8853634077f425f4b2077f4ca0c986e4ac0e549a8601859b0578a3ccbdbdd5d4 +size 405 diff --git a/tests/Images/Input/Gif/max-width.gif b/tests/Images/Input/Gif/max-width.gif new file mode 100644 index 0000000000..bb0e131acc --- /dev/null +++ b/tests/Images/Input/Gif/max-width.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:008e2a84afed6c31b6635aa9d8c7ee2176f01a0eb0a04143883a8533d7ca33c9 +size 405 diff --git a/tests/Images/Input/Gif/receipt.gif b/tests/Images/Input/Gif/receipt.gif new file mode 100644 index 0000000000..ce800a8197 --- /dev/null +++ b/tests/Images/Input/Gif/receipt.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bac212852eee73f3c29f30be0be375e5caccbe86e5f4adfaa8c0a7a3673a91ab +size 50686 diff --git a/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg b/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg new file mode 100644 index 0000000000..9300dced9a --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7508a28e39026ed8ebc9751138d014450b2f636a343838d8e08dbc7e19ad6df +size 18329 diff --git a/tests/Images/Input/Jpg/baseline/iptc.jpg b/tests/Images/Input/Jpg/baseline/iptc.jpg new file mode 100644 index 0000000000..adb12621fb --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/iptc.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c8a0747d9282bfd7e8e7f4a0119c53c702bf600384b786ef9b5263457f38ada +size 18611 diff --git a/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg b/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg new file mode 100644 index 0000000000..6cc3531a0b --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1507746a6c37697cb985fc7427709fd68478ff7cbdfe20f6cfbe7257ed6c7ccd +size 39149 diff --git a/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg b/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg new file mode 100644 index 0000000000..3880b869ed --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90079722b2763f64ff7a47889a7775c9b63ed92239aeff4df437bd1b5a5ab540 +size 618142 diff --git a/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg b/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg new file mode 100644 index 0000000000..b3abb7d2f5 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f313e48c5b9d4d1af2df44057970f03ddafc809862d14e8593f3e1fc0aef2c1 +size 718443 diff --git a/tests/Images/Input/Png/Bradley01.png b/tests/Images/Input/Png/Bradley01.png new file mode 100644 index 0000000000..5af2913e60 --- /dev/null +++ b/tests/Images/Input/Png/Bradley01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7eddc690c9d50fcaca3b0045d225b08c2fb172ceff5eead1d476c4df0354d02 +size 25266 diff --git a/tests/Images/Input/Png/Bradley02.png b/tests/Images/Input/Png/Bradley02.png new file mode 100644 index 0000000000..917bf9310f --- /dev/null +++ b/tests/Images/Input/Png/Bradley02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a73ebf6e35d5336bdf194d5098bcbe0ad240bbd09cd357816aacb1e0e7e6a614 +size 26467 diff --git a/tests/Images/Input/Png/PngWithMetaData.png b/tests/Images/Input/Png/PngWithMetaData.png index 54c08ca42c..8db95fa632 100644 --- a/tests/Images/Input/Png/PngWithMetaData.png +++ b/tests/Images/Input/Png/PngWithMetaData.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0490f627b22a3487b78e2797ebb65f5741fdbabfd4a3d9db806ca624f62fe8c -size 805 +oid sha256:a37d2d31c2148b94bfd732c8964808dcc2dcdb6d2c187bb5d0403dc09af9ab46 +size 60544 diff --git a/tests/Images/Input/Png/basn3p01.png b/tests/Images/Input/Png/basn3p01.png new file mode 100644 index 0000000000..15673642fa --- /dev/null +++ b/tests/Images/Input/Png/basn3p01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2d7cd682df5f74506b33a5d70c344aaee248fda79fdfef8e873426fd6f2b75b +size 112 diff --git a/tests/Images/Input/Png/basn3p02.png b/tests/Images/Input/Png/basn3p02.png new file mode 100644 index 0000000000..1065847eff --- /dev/null +++ b/tests/Images/Input/Png/basn3p02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0466bb7ed9984cf03b70704564bcffab1df8ec0e8167473ba0f75e4fedce5a8f +size 146 diff --git a/tests/Images/Input/Png/basn3p04.png b/tests/Images/Input/Png/basn3p04.png new file mode 100644 index 0000000000..05e361b1e5 --- /dev/null +++ b/tests/Images/Input/Png/basn3p04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1fc7be978d3149b98533d0076245ae64353b7967290f4204c1282ecb4ec1aba +size 216 diff --git a/tests/Images/Input/Png/basn3p08.png b/tests/Images/Input/Png/basn3p08.png new file mode 100644 index 0000000000..68cb909bfb --- /dev/null +++ b/tests/Images/Input/Png/basn3p08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d58256cd2eb16b5740d4c1403d25ce43d8dd03e270627ab709d2fb141e3d904c +size 1286 diff --git a/tests/Images/Input/Png/bike-small.png b/tests/Images/Input/Png/bike-small.png new file mode 100644 index 0000000000..46eee03cf6 --- /dev/null +++ b/tests/Images/Input/Png/bike-small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fb48e3c495d7834df09a17d6a6cadbce047a0e791b0cb78ca3a6d334d309b13 +size 75628 diff --git a/tests/Images/Input/Png/david.png b/tests/Images/Input/Png/david.png new file mode 100644 index 0000000000..c1e3b5cd5a --- /dev/null +++ b/tests/Images/Input/Png/david.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7e3b46a2f62251950f8c17f94c9d9a434ae643a98c058679644b5a0c5633b6 +size 27218 diff --git a/tests/Images/Input/Png/issues/Issue_1014_1.png b/tests/Images/Input/Png/issues/Issue_1014_1.png new file mode 100644 index 0000000000..2bdd826d63 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e986d0ff909fc92b0f325c6012ca4123674b239c54647bbdf3fc0c7ace3e4327 +size 3965 diff --git a/tests/Images/Input/Png/issues/Issue_1014_2.png b/tests/Images/Input/Png/issues/Issue_1014_2.png new file mode 100644 index 0000000000..224ee915ae --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e042b3aa6c17db70a9fe7fda4fae1c90388d32480ac44e9f87341279c845fd11 +size 6626 diff --git a/tests/Images/Input/Png/issues/Issue_1014_3.png b/tests/Images/Input/Png/issues/Issue_1014_3.png new file mode 100644 index 0000000000..b288f4380e --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ca6190682c99ec1c00fe2bac7fee86902bcf8a2db43a781452bf137eb1b962a +size 4157 diff --git a/tests/Images/Input/Png/issues/Issue_1014_4.png b/tests/Images/Input/Png/issues/Issue_1014_4.png new file mode 100644 index 0000000000..1fb3dd5397 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03c6ee008225ac18f2966842772d5afa64e318f41b552c3d63d1a9fdd3544eff +size 3328 diff --git a/tests/Images/Input/Png/issues/Issue_1014_5.png b/tests/Images/Input/Png/issues/Issue_1014_5.png new file mode 100644 index 0000000000..98a06472cc --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1163cd03933f2b23e1ae27baa848a5c2d3f41f0b3457a33dd9523c40e610076b +size 4085 diff --git a/tests/Images/Input/Png/issues/Issue_1014_6.png b/tests/Images/Input/Png/issues/Issue_1014_6.png new file mode 100644 index 0000000000..77871b29c7 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1014_6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c60f7be42794764f970149dd967bcd15c2f7f783b82a3edf528e7556eeb6c806 +size 2114 diff --git a/tests/Images/Input/Png/issues/Issue_1047.png b/tests/Images/Input/Png/issues/Issue_1047.png new file mode 100644 index 0000000000..7d5a53a9e5 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1047.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4768d4bc3a4aaddb8e3e5cbff2beb706abacfd5448d658564f001811dafd320a +size 44638 diff --git a/tests/Images/Input/Png/issues/Issue_1127.png b/tests/Images/Input/Png/issues/Issue_1127.png new file mode 100644 index 0000000000..6101102e5d --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1127.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9913e68387bb596198089315ffd8e01d27356493f78b26add68b5cf37183b239 +size 13855 diff --git a/tests/Images/Input/Png/issues/Issue_1177_1.png b/tests/Images/Input/Png/issues/Issue_1177_1.png new file mode 100644 index 0000000000..2d851e31bf --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1177_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cef2be6012f4604f9f30b51273661058df0201be4de508235f372eb2304b2132 +size 7023 diff --git a/tests/Images/Input/Png/issues/Issue_1177_2.png b/tests/Images/Input/Png/issues/Issue_1177_2.png new file mode 100644 index 0000000000..efd043b38c --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_1177_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7067af724977e1ecd8fc761f50226eaaa9e9d4142be963b4edbbf0918b8eba1d +size 57125 diff --git a/tests/Images/Input/Png/issues/Issue_410.png b/tests/Images/Input/Png/issues/Issue_410.png new file mode 100644 index 0000000000..1ca3be3eaa --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_410.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe3c66fb0f52b989f7398bc6bcaa18e83625120a53b4972023705a7a5925eab1 +size 674 diff --git a/tests/Images/Input/Png/xc1n0g08.png b/tests/Images/Input/Png/xc1n0g08.png new file mode 100644 index 0000000000..2afec8533f --- /dev/null +++ b/tests/Images/Input/Png/xc1n0g08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4059f7e6a1c5bac1801f70e09f9ec1e1297dcdce34055c13ab2703d6d9613c7e +size 138 diff --git a/tests/Images/Input/Png/xc9n2c08.png b/tests/Images/Input/Png/xc9n2c08.png new file mode 100644 index 0000000000..549a4924af --- /dev/null +++ b/tests/Images/Input/Png/xc9n2c08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e252a0e7df3e794e52ce4a831edafef76e7043d0d8d84019db0f7fd0b30e20f4 +size 145 diff --git a/tests/Images/Input/Png/xd0n2c08.png b/tests/Images/Input/Png/xd0n2c08.png new file mode 100644 index 0000000000..df7548a6db --- /dev/null +++ b/tests/Images/Input/Png/xd0n2c08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1287690808e809dc5d4fb89d8a7fd69ed93521f290abd42021ca00a061a1ba4 +size 145 diff --git a/tests/Images/Input/Png/xd3n2c08.png b/tests/Images/Input/Png/xd3n2c08.png new file mode 100644 index 0000000000..db5cec0c4b --- /dev/null +++ b/tests/Images/Input/Png/xd3n2c08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00b53c3bbd0641454521b982bc6f6bcfda7c91f1874cefb3a9bac37d80a1a269 +size 145 diff --git a/tests/Images/Input/Png/xdtn0g01.png b/tests/Images/Input/Png/xdtn0g01.png new file mode 100644 index 0000000000..96c906fa8e --- /dev/null +++ b/tests/Images/Input/Png/xdtn0g01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9d1fb2a708703518368c392c74765a6e3e5b49dbb9717df3974452291032df9 +size 61 diff --git a/tests/Images/Input/Tga/16bit_noalphabits.tga b/tests/Images/Input/Tga/16bit_noalphabits.tga new file mode 100644 index 0000000000..cff4abf945 --- /dev/null +++ b/tests/Images/Input/Tga/16bit_noalphabits.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7a71e04cb2c335fb46bb91c6bf71e32deafe6a65b701e9fbdb1f95ec69a432c +size 96818 diff --git a/tests/Images/Input/Tga/16bit_rle_noalphabits.tga b/tests/Images/Input/Tga/16bit_rle_noalphabits.tga new file mode 100644 index 0000000000..b1bbb8c548 --- /dev/null +++ b/tests/Images/Input/Tga/16bit_rle_noalphabits.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c605b2ef72f8e54530cb3f0922527ee2754adab8d158276931ec7e2842f2644 +size 138354 diff --git a/tests/Images/Input/Tga/32bit_no_alphabits.tga b/tests/Images/Input/Tga/32bit_no_alphabits.tga new file mode 100644 index 0000000000..903eca4594 --- /dev/null +++ b/tests/Images/Input/Tga/32bit_no_alphabits.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0aea1128a1bd7477dfa0d007a1eba25907be24847284c48a5f9fbd61bcea3cf0 +size 61522 diff --git a/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga new file mode 100644 index 0000000000..b21dad5e0d --- /dev/null +++ b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98a198392bd527523f8649d6126af81e5a588ad7265dc3d48a1da7b5a6cb6ff7 +size 230578 diff --git a/tests/Images/Input/Tga/ccm8.tga b/tests/Images/Input/Tga/ccm8.tga new file mode 100644 index 0000000000..ab92516355 --- /dev/null +++ b/tests/Images/Input/Tga/ccm8.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67b3ffaaa75561d8b959258d6b26a1f9ca3228b02a3df98a614ea43241aaea52 +size 9271 diff --git a/tests/Images/Input/Tga/grayscale_LL.tga b/tests/Images/Input/Tga/grayscale_LL.tga new file mode 100644 index 0000000000..13ae52c37e --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74ef200d90078b5cd8ff6ddf714e0a082fc420684e2d7667fe158c5705b91946 +size 65580 diff --git a/tests/Images/Input/Tga/grayscale_LR.tga b/tests/Images/Input/Tga/grayscale_LR.tga new file mode 100644 index 0000000000..01c71b81c5 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed269c8f3bb462d963188d7352ebe85ab20357ac7803e5ac4d7110a23b9e6ddb +size 65580 diff --git a/tests/Images/Input/Tga/grayscale_UL.tga b/tests/Images/Input/Tga/grayscale_UL.tga new file mode 100644 index 0000000000..7670e83f1d --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72c6e1e09b923455e0c8cd14c37b358eb578bc14a0a8fcedde3ab81769960eb7 +size 65580 diff --git a/tests/Images/Input/Tga/grayscale_UR.tga b/tests/Images/Input/Tga/grayscale_UR.tga new file mode 100644 index 0000000000..a33d3aa2e1 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8831036fdb79dbc9fa9d6940c6bb4bfc546b83f9caf55a65853e9a60639edece +size 65580 diff --git a/tests/Images/Input/Tga/grayscale_a_LL.tga b/tests/Images/Input/Tga/grayscale_a_LL.tga new file mode 100644 index 0000000000..ebc3781349 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e90d280ddfde2d147dd68bacf7bb31e9133f8132adcbe50c841950d5a7834b8e +size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_LR.tga b/tests/Images/Input/Tga/grayscale_a_LR.tga new file mode 100644 index 0000000000..1d142b5c1d --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df0cd7261a98e87700e4f9c1328d73ee9f278c4e538895ab0a97b88392156523 +size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_UL.tga b/tests/Images/Input/Tga/grayscale_a_UL.tga new file mode 100644 index 0000000000..bd6c256270 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:debc2bb439a72f5cae3f0fdb525dbc0b3488abc27cee81d1eb73cb97765a07f3 +size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_UR.tga b/tests/Images/Input/Tga/grayscale_a_UR.tga new file mode 100644 index 0000000000..ce2bf4dc82 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff8cdd9cf4aa48f0df2d920483aeead476166e0e958d07aa5b8a3cd2babfd834 +size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_LL.tga b/tests/Images/Input/Tga/grayscale_a_rle_LL.tga new file mode 100644 index 0000000000..3434cc86cb --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d65c2b9caf83b2eb063e820e15944621dec324f8278ae6b60b088dc380a2c40b +size 54102 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_LR.tga b/tests/Images/Input/Tga/grayscale_a_rle_LR.tga new file mode 100644 index 0000000000..75850f39cf --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f7e06f04de22ecbf8fea1da72c6a6feb45161e92580e96ca5c4482ec3bc00de +size 54237 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_UL.tga b/tests/Images/Input/Tga/grayscale_a_rle_UL.tga new file mode 100644 index 0000000000..ed77308e56 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8555c8dcfa7ac65ad9f1d2389d82ee21dd90329b7200e10a457abc0f67d18ac8 +size 54295 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_UR.tga b/tests/Images/Input/Tga/grayscale_a_rle_UR.tga new file mode 100644 index 0000000000..04945dc617 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_a_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9abc35a5e6ef0aaa29a5d0bd7cef30281b1d94fec669e884cc382a2d73b359a0 +size 54052 diff --git a/tests/Images/Input/Tga/grayscale_rle_LR.tga b/tests/Images/Input/Tga/grayscale_rle_LR.tga new file mode 100644 index 0000000000..766d3884c9 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_rle_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a897be6870be2cd183e7678e954767fd12a763c7bfce0f2246f1b7cc1ad08804 +size 31165 diff --git a/tests/Images/Input/Tga/grayscale_rle_UL.tga b/tests/Images/Input/Tga/grayscale_rle_UL.tga new file mode 100644 index 0000000000..699e7ae5b8 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_rle_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f11be4af2283059e869543949588fe19db0e36dec64157ad9a61711cb5e6428 +size 31198 diff --git a/tests/Images/Input/Tga/grayscale_rle_UR.tga b/tests/Images/Input/Tga/grayscale_rle_UR.tga new file mode 100644 index 0000000000..c61503db81 --- /dev/null +++ b/tests/Images/Input/Tga/grayscale_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5aa67ec6d3408fd469ec8e7c5613daf130be893e0b76dee2994a2c32ddae471 +size 31054 diff --git a/tests/Images/Input/Tga/indexed_LR.tga b/tests/Images/Input/Tga/indexed_LR.tga new file mode 100644 index 0000000000..659c3bcea8 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6d5219fadf7d8b743d35c7e16f11e1182f76351757ff962e0a27f81c357b1fb +size 66315 diff --git a/tests/Images/Input/Tga/indexed_UL.tga b/tests/Images/Input/Tga/indexed_UL.tga new file mode 100644 index 0000000000..da2a3f8ef9 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f42dd07528f9e4f7914a570c027cc845edfe6d3fcdfa45ec8f21bc254cc1f1f +size 66315 diff --git a/tests/Images/Input/Tga/indexed_UR.tga b/tests/Images/Input/Tga/indexed_UR.tga new file mode 100644 index 0000000000..a497383ab8 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90d8caa10d3a05f845f94b176a77a2ed85e25b3d460527c96abfe793870c89b8 +size 66315 diff --git a/tests/Images/Input/Tga/indexed_a_LL.tga b/tests/Images/Input/Tga/indexed_a_LL.tga new file mode 100644 index 0000000000..e074f253b1 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1522f4513cadd35869f39e171b1dccda9181da5b812d487e2a3e17308722d7c0 +size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_LR.tga b/tests/Images/Input/Tga/indexed_a_LR.tga new file mode 100644 index 0000000000..aa361fa74d --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d01d5c89e772582a30ef9d528928cc313474a54b7f5530947a637adea95a4536 +size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_UL.tga b/tests/Images/Input/Tga/indexed_a_UL.tga new file mode 100644 index 0000000000..19b0b36fc2 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa4d93b76ddcfa82a8ef02921e1c90dbd136de45608e7e7502c2d2256736f9ae +size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_UR.tga b/tests/Images/Input/Tga/indexed_a_UR.tga new file mode 100644 index 0000000000..9b783a88aa --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:feab3d418ab68eef0b40282de0e00c126fedff31f8657159799efef9b6f4a2af +size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_rle_LL.tga b/tests/Images/Input/Tga/indexed_a_rle_LL.tga new file mode 100644 index 0000000000..147cc91011 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2be79621e93dfdbd3ec9bea5085675719429cb264b1f9bbafa4ab2c9da28f677 +size 31665 diff --git a/tests/Images/Input/Tga/indexed_a_rle_LR.tga b/tests/Images/Input/Tga/indexed_a_rle_LR.tga new file mode 100644 index 0000000000..6859107d0d --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:419d28012037d85794d6839fc8bdaa4b830daf8d078b536a655dc65370c15a38 +size 31776 diff --git a/tests/Images/Input/Tga/indexed_a_rle_UL.tga b/tests/Images/Input/Tga/indexed_a_rle_UL.tga new file mode 100644 index 0000000000..be44253d20 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:892b19c5e4da9ba4b96d3458d2ee35e1f64ca65e8f8f8b6eebb284e83a6bceab +size 31765 diff --git a/tests/Images/Input/Tga/indexed_a_rle_UR.tga b/tests/Images/Input/Tga/indexed_a_rle_UR.tga new file mode 100644 index 0000000000..b308ff7347 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_a_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42586d5d45bb922671755d019fe8d5f76c10ab856fcf6521fb7d114fba118c71 +size 31666 diff --git a/tests/Images/Input/Tga/indexed_rle_LL.tga b/tests/Images/Input/Tga/indexed_rle_LL.tga new file mode 100644 index 0000000000..6576d515a0 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3dbf4ae9566e00d2165d74f3c17208853954b4c1f1cbc4ebc321fe3adb29676 +size 30549 diff --git a/tests/Images/Input/Tga/indexed_rle_LR.tga b/tests/Images/Input/Tga/indexed_rle_LR.tga new file mode 100644 index 0000000000..2c14e37644 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32461dcf64ec2f6ccf6e17a7209c769a5594b8c94a31de7cc693d6abe6ba2081 +size 30610 diff --git a/tests/Images/Input/Tga/indexed_rle_UL.tga b/tests/Images/Input/Tga/indexed_rle_UL.tga new file mode 100644 index 0000000000..0a06b3a865 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:841f05e9f8ecdade8c992b830b9bf5893494f41accb0f0fec30f4692866c1675 +size 30640 diff --git a/tests/Images/Input/Tga/indexed_rle_UR.tga b/tests/Images/Input/Tga/indexed_rle_UR.tga new file mode 100644 index 0000000000..1e68e545e7 --- /dev/null +++ b/tests/Images/Input/Tga/indexed_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a709594fd475dbe042c16671959bfbc0031e64db8e74375f01c29685d2e384ec +size 30500 diff --git a/tests/Images/Input/Tga/rgb15.tga b/tests/Images/Input/Tga/rgb15.tga new file mode 100644 index 0000000000..870295b45a --- /dev/null +++ b/tests/Images/Input/Tga/rgb15.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:390cfff190bc41386fa134eca70ea0d3ffdc32a285c73278ed34046b09c46c9d +size 80537 diff --git a/tests/Images/Input/Tga/rgb15rle.tga b/tests/Images/Input/Tga/rgb15rle.tga new file mode 100644 index 0000000000..a45940fc98 --- /dev/null +++ b/tests/Images/Input/Tga/rgb15rle.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3219186fc9a9f859c99c2b31cf81e7f0ab4292956d22fc659e714d0cdb51cfa7 +size 19941 diff --git a/tests/Images/Input/Tga/rgb24_top_left.tga b/tests/Images/Input/Tga/rgb24_top_left.tga new file mode 100644 index 0000000000..bfaeae686c --- /dev/null +++ b/tests/Images/Input/Tga/rgb24_top_left.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9c0aed8fb8c4e336fb1b9a6b76c9ba3e81554469191293e0b07d6afc8d9086a +size 12332 diff --git a/tests/Images/Input/Tga/rgb_LR.tga b/tests/Images/Input/Tga/rgb_LR.tga new file mode 100644 index 0000000000..bb6a8a9c8c --- /dev/null +++ b/tests/Images/Input/Tga/rgb_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a57a4f63dbe50b43e95cfcffff0ecf981de91268c44064b73c94c295f0909fea +size 196652 diff --git a/tests/Images/Input/Tga/rgb_UR.tga b/tests/Images/Input/Tga/rgb_UR.tga new file mode 100644 index 0000000000..b7a7754fea --- /dev/null +++ b/tests/Images/Input/Tga/rgb_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dc5882241cd3513795cfcb207b7b4b6014585cf50504e01f968f1db9ad7d8d8 +size 196652 diff --git a/tests/Images/Input/Tga/rgb_a_LL.tga b/tests/Images/Input/Tga/rgb_a_LL.tga new file mode 100644 index 0000000000..786eb7b7d3 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_LL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eff46c35b08b02759b5e5cf4ba473b7714cf303e35cd93ae1404b8e3277014a1 +size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_LR.tga b/tests/Images/Input/Tga/rgb_a_LR.tga new file mode 100644 index 0000000000..312af4c0de --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b91c063644c2f21f74fa88687a05f8730366e75a896bf21630af280abc9950b +size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_UL.tga b/tests/Images/Input/Tga/rgb_a_UL.tga new file mode 100644 index 0000000000..7ee3a52128 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a167af1f8d64119e206593f8944c0b7901393a1b97d703c0121b8a59cae03f4 +size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_UR.tga b/tests/Images/Input/Tga/rgb_a_UR.tga new file mode 100644 index 0000000000..12d7b5a798 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d88b70ad8878d44e29f680716670dd876771620264bdf2af9179284508fcc03 +size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_rle_LR.tga b/tests/Images/Input/Tga/rgb_a_rle_LR.tga new file mode 100644 index 0000000000..ceac831b82 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_rle_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bcfe104b6c56ddaa06bfaca4a2a9b070e7af8f74dc433736d6b0e536bf3c0b6 +size 98317 diff --git a/tests/Images/Input/Tga/rgb_a_rle_UL.tga b/tests/Images/Input/Tga/rgb_a_rle_UL.tga new file mode 100644 index 0000000000..0ea58fd1d6 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_rle_UL.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be1323021deead462ef38c17eea5d59aea7467ae33b91bd65b542085e74aa4e4 +size 98427 diff --git a/tests/Images/Input/Tga/rgb_a_rle_UR.tga b/tests/Images/Input/Tga/rgb_a_rle_UR.tga new file mode 100644 index 0000000000..e6eebbdaff --- /dev/null +++ b/tests/Images/Input/Tga/rgb_a_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cec69308cbfd13f1cae79462fcfd013655d27fb6386e60e6801a8fbb58685201 +size 97990 diff --git a/tests/Images/Input/Tga/rgb_rle_LR.tga b/tests/Images/Input/Tga/rgb_rle_LR.tga new file mode 100644 index 0000000000..11146a812f --- /dev/null +++ b/tests/Images/Input/Tga/rgb_rle_LR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c21355f73ed5f78ec2835c3e8bb11b1d48bc5b360a804555a49a435077e8bcb +size 73337 diff --git a/tests/Images/Input/Tga/rgb_rle_UR.tga b/tests/Images/Input/Tga/rgb_rle_UR.tga new file mode 100644 index 0000000000..4c9e540d37 --- /dev/null +++ b/tests/Images/Input/Tga/rgb_rle_UR.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5d56b7e72b59624545b405406daeb9a578ff3da6e1ea99ee759ace6909da6d6 +size 73086 diff --git a/tests/Images/Input/Tga/targa_16bit.tga b/tests/Images/Input/Tga/targa_16bit.tga new file mode 100644 index 0000000000..6c4143c2ee --- /dev/null +++ b/tests/Images/Input/Tga/targa_16bit.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3adea897f8843b73d0042e23bdfbd0115a7f534df90699134e768df57061f46 +size 70518 diff --git a/tests/Images/Input/Tga/targa_16bit_pal.tga b/tests/Images/Input/Tga/targa_16bit_pal.tga new file mode 100644 index 0000000000..b25def7798 --- /dev/null +++ b/tests/Images/Input/Tga/targa_16bit_pal.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97a4ac0cecfe69e1b5c74db5288fb8ca3bf29968e3b5288c4e5ce03bb4f06915 +size 35780 diff --git a/tests/Images/Input/Tga/targa_16bit_rle.tga b/tests/Images/Input/Tga/targa_16bit_rle.tga new file mode 100644 index 0000000000..49ef0e998b --- /dev/null +++ b/tests/Images/Input/Tga/targa_16bit_rle.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47d7ebf37672ea846ce071155733697e34083de36aeaafaebd78317708feffde +size 19566 diff --git a/tests/Images/Input/Tga/targa_24bit.tga b/tests/Images/Input/Tga/targa_24bit.tga new file mode 100644 index 0000000000..82c22e2425 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35921b6250e43ba8e1fb125ebe4939a57a67efb0aa9eac0d3605bf90e93309b1 +size 105768 diff --git a/tests/Images/Input/Tga/targa_24bit_pal.tga b/tests/Images/Input/Tga/targa_24bit_pal.tga new file mode 100644 index 0000000000..abfbf588a6 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_pal.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4926969e5ae6c9af38d33fa18429de756c48d06edd87c5d27cb8d5232b066ab2 +size 36036 diff --git a/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga new file mode 100644 index 0000000000..b8c4071745 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1c52e538a7d134b20ff57e44b7e304d1b5effacac03a4481d169702796fb195 +size 36062 diff --git a/tests/Images/Input/Tga/targa_24bit_rle.tga b/tests/Images/Input/Tga/targa_24bit_rle.tga new file mode 100644 index 0000000000..d6af44c0a6 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_rle.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56a79ab92d84bbe8c7efbc2711051938fa3ba97b48830aea0cb1dafd7d1fe222 +size 37711 diff --git a/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga new file mode 100644 index 0000000000..9310c51a70 --- /dev/null +++ b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30e8b6d01ebf9d227d2e9dcdd7b2641bf8f335107110dfff780351870217d4f4 +size 37102 diff --git a/tests/Images/Input/Tga/targa_32bit.tga b/tests/Images/Input/Tga/targa_32bit.tga new file mode 100644 index 0000000000..8b2a57c810 --- /dev/null +++ b/tests/Images/Input/Tga/targa_32bit.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3a220619e25e86bab01b01a2e231ee64fd004e047fa86016bf68de576877352 +size 141018 diff --git a/tests/Images/Input/Tga/targa_32bit_rle.tga b/tests/Images/Input/Tga/targa_32bit_rle.tga new file mode 100644 index 0000000000..b021a2cc15 --- /dev/null +++ b/tests/Images/Input/Tga/targa_32bit_rle.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f415d6a246909c18fe604248ab5fe27c74aff9a63df58d8cdeab7c4c3cbe056a +size 49994 diff --git a/tests/Images/Input/Tga/targa_8bit.tga b/tests/Images/Input/Tga/targa_8bit.tga new file mode 100644 index 0000000000..9b0512971e --- /dev/null +++ b/tests/Images/Input/Tga/targa_8bit.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6aaae46d0e55f32a72732fbe48ed9dc4044c53432999ab66e9475e45e40f0133 +size 35268 diff --git a/tests/Images/Input/Tga/targa_8bit_rle.tga b/tests/Images/Input/Tga/targa_8bit_rle.tga new file mode 100644 index 0000000000..d6a66def15 --- /dev/null +++ b/tests/Images/Input/Tga/targa_8bit_rle.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a18d7fd98bc9ab62276103b4e7b474be93b3d7241f4f06aa564e32150e205a71 +size 13145 diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings new file mode 100644 index 0000000000..ee408a5f04 --- /dev/null +++ b/tests/coverlet.runsettings @@ -0,0 +1,14 @@ + + + + + + + lcov + [SixLabors.*]* + true + + + + +