mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
577 changed files with 20790 additions and 6810 deletions
@ -1,8 +1,5 @@ |
|||||
blank_issues_enabled: false |
blank_issues_enabled: false |
||||
contact_links: |
contact_links: |
||||
- name: Ask a Question |
|
||||
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AQ%26A |
|
||||
about: Ask a question about this project. |
|
||||
- name: Feature Request |
- name: Feature Request |
||||
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas |
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas |
||||
about: Share ideas for new features for this project. |
about: Share ideas for new features for this project. |
||||
|
|||||
@ -1,202 +1,196 @@ |
|||||
name: Build |
name: Build |
||||
|
|
||||
on: |
on: |
||||
push: |
push: |
||||
branches: |
branches: |
||||
- master |
- master |
||||
tags: |
tags: |
||||
- "v*" |
- "v*" |
||||
pull_request: |
pull_request: |
||||
branches: |
branches: |
||||
- master |
- master |
||||
jobs: |
jobs: |
||||
Build: |
Build: |
||||
strategy: |
strategy: |
||||
matrix: |
matrix: |
||||
options: |
options: |
||||
- os: ubuntu-latest |
- os: ubuntu-latest |
||||
framework: net6.0 |
framework: net6.0 |
||||
sdk: 6.0.x |
sdk: 6.0.x |
||||
sdk-preview: true |
sdk-preview: true |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: macos-latest |
- os: macos-latest |
||||
framework: net6.0 |
framework: net6.0 |
||||
sdk: 6.0.x |
sdk: 6.0.x |
||||
sdk-preview: true |
sdk-preview: true |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net6.0 |
framework: net6.0 |
||||
sdk: 6.0.x |
sdk: 6.0.x |
||||
sdk-preview: true |
sdk-preview: true |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: ubuntu-latest |
- os: ubuntu-latest |
||||
framework: net5.0 |
framework: net5.0 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: macos-latest |
- os: macos-latest |
||||
framework: net5.0 |
framework: net5.0 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net5.0 |
framework: net5.0 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: ubuntu-latest |
- os: ubuntu-latest |
||||
framework: netcoreapp3.1 |
framework: netcoreapp3.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: true |
codecov: false |
||||
- os: macos-latest |
- os: macos-latest |
||||
framework: netcoreapp3.1 |
framework: netcoreapp3.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: netcoreapp3.1 |
framework: netcoreapp3.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: netcoreapp2.1 |
framework: netcoreapp2.1 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net472 |
framework: net472 |
||||
runtime: -x64 |
runtime: -x64 |
||||
codecov: false |
codecov: false |
||||
- os: windows-latest |
- os: windows-latest |
||||
framework: net472 |
framework: net472 |
||||
runtime: -x86 |
runtime: -x86 |
||||
codecov: false |
codecov: false |
||||
|
|
||||
runs-on: ${{matrix.options.os}} |
runs-on: ${{matrix.options.os}} |
||||
|
|
||||
steps: |
steps: |
||||
- name: Git Config |
- name: Git Config |
||||
shell: bash |
shell: bash |
||||
run: | |
run: | |
||||
git config --global core.autocrlf false |
git config --global core.autocrlf false |
||||
git config --global core.longpaths true |
git config --global core.longpaths true |
||||
|
|
||||
- name: Git Checkout |
- name: Git Checkout |
||||
uses: actions/checkout@v2 |
uses: actions/checkout@v2 |
||||
with: |
with: |
||||
fetch-depth: 0 |
fetch-depth: 0 |
||||
submodules: recursive |
submodules: recursive |
||||
|
|
||||
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 |
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 |
||||
- name: Git Create LFS FileList |
- name: Git Create LFS FileList |
||||
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id |
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id |
||||
|
|
||||
- name: Git Setup LFS Cache |
- name: Git Setup LFS Cache |
||||
uses: actions/cache@v2 |
uses: actions/cache@v2 |
||||
id: lfs-cache |
id: lfs-cache |
||||
with: |
with: |
||||
path: .git/lfs |
path: .git/lfs |
||||
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 |
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 |
||||
|
|
||||
- name: Git Pull LFS |
- name: Git Pull LFS |
||||
run: git lfs pull |
run: git lfs pull |
||||
|
|
||||
- name: NuGet Install |
- name: NuGet Install |
||||
uses: NuGet/setup-nuget@v1 |
uses: NuGet/setup-nuget@v1 |
||||
|
|
||||
- name: NuGet Setup Cache |
- name: NuGet Setup Cache |
||||
uses: actions/cache@v2 |
uses: actions/cache@v2 |
||||
id: nuget-cache |
id: nuget-cache |
||||
with: |
with: |
||||
path: ~/.nuget |
path: ~/.nuget |
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
||||
restore-keys: ${{ runner.os }}-nuget- |
restore-keys: ${{ runner.os }}-nuget- |
||||
|
|
||||
- name: DotNet Setup Preview |
- name: DotNet Setup Preview |
||||
if: ${{ matrix.options.sdk-preview == true }} |
if: ${{ matrix.options.sdk-preview == true }} |
||||
uses: actions/setup-dotnet@v1 |
uses: actions/setup-dotnet@v1 |
||||
with: |
with: |
||||
dotnet-version: ${{ matrix.options.sdk }} |
dotnet-version: ${{ matrix.options.sdk }} |
||||
include-prerelease: true |
include-prerelease: true |
||||
|
|
||||
- name: DotNet Build |
- name: DotNet Build |
||||
if: ${{ matrix.options.sdk-preview != true }} |
if: ${{ matrix.options.sdk-preview != true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING: True |
SIXLABORS_TESTING: True |
||||
|
|
||||
- name: DotNet Build Preview |
- name: DotNet Build Preview |
||||
if: ${{ matrix.options.sdk-preview == true }} |
if: ${{ matrix.options.sdk-preview == true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING_PREVIEW: True |
SIXLABORS_TESTING_PREVIEW: True |
||||
|
|
||||
- name: DotNet Test |
- name: DotNet Test |
||||
if: ${{ matrix.options.sdk-preview != true }} |
if: ${{ matrix.options.sdk-preview != true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING: True |
SIXLABORS_TESTING: True |
||||
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
||||
|
|
||||
- name: DotNet Test Preview |
- name: DotNet Test Preview |
||||
if: ${{ matrix.options.sdk-preview == true }} |
if: ${{ matrix.options.sdk-preview == true }} |
||||
shell: pwsh |
shell: pwsh |
||||
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
||||
env: |
env: |
||||
SIXLABORS_TESTING_PREVIEW: True |
SIXLABORS_TESTING_PREVIEW: True |
||||
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
||||
|
|
||||
- name: Export Failed Output |
- name: Export Failed Output |
||||
uses: actions/upload-artifact@v2 |
uses: actions/upload-artifact@v2 |
||||
if: failure() |
if: failure() |
||||
with: |
with: |
||||
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip |
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip |
||||
path: tests/Images/ActualOutput/ |
path: tests/Images/ActualOutput/ |
||||
|
|
||||
- name: Codecov Update |
Publish: |
||||
uses: codecov/codecov-action@v1 |
needs: [Build] |
||||
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') |
|
||||
with: |
runs-on: ubuntu-latest |
||||
flags: unittests |
|
||||
|
if: (github.event_name == 'push') |
||||
Publish: |
|
||||
needs: [Build] |
steps: |
||||
|
- name: Git Config |
||||
runs-on: ubuntu-latest |
shell: bash |
||||
|
run: | |
||||
if: (github.event_name == 'push') |
git config --global core.autocrlf false |
||||
|
git config --global core.longpaths true |
||||
steps: |
|
||||
- name: Git Config |
- name: Git Checkout |
||||
shell: bash |
uses: actions/checkout@v2 |
||||
run: | |
with: |
||||
git config --global core.autocrlf false |
fetch-depth: 0 |
||||
git config --global core.longpaths true |
submodules: recursive |
||||
|
|
||||
- name: Git Checkout |
- name: NuGet Install |
||||
uses: actions/checkout@v2 |
uses: NuGet/setup-nuget@v1 |
||||
with: |
|
||||
fetch-depth: 0 |
- name: NuGet Setup Cache |
||||
submodules: recursive |
uses: actions/cache@v2 |
||||
|
id: nuget-cache |
||||
- name: NuGet Install |
with: |
||||
uses: NuGet/setup-nuget@v1 |
path: ~/.nuget |
||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
||||
- name: NuGet Setup Cache |
restore-keys: ${{ runner.os }}-nuget- |
||||
uses: actions/cache@v2 |
|
||||
id: nuget-cache |
- name: DotNet Pack |
||||
with: |
shell: pwsh |
||||
path: ~/.nuget |
run: ./ci-pack.ps1 |
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
|
||||
restore-keys: ${{ runner.os }}-nuget- |
- name: MyGet Publish |
||||
|
shell: pwsh |
||||
- name: DotNet Pack |
run: | |
||||
shell: pwsh |
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package |
||||
run: ./ci-pack.ps1 |
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json |
||||
|
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org |
||||
- name: MyGet Publish |
|
||||
shell: pwsh |
|
||||
run: | |
|
||||
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package |
|
||||
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json |
|
||||
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org |
|
||||
|
|||||
@ -0,0 +1,81 @@ |
|||||
|
name: CodeCoverage |
||||
|
|
||||
|
on: |
||||
|
schedule: |
||||
|
# 2AM every Tuesday/Thursday |
||||
|
- cron: "0 2 * * 2,4" |
||||
|
jobs: |
||||
|
Build: |
||||
|
strategy: |
||||
|
matrix: |
||||
|
options: |
||||
|
- os: ubuntu-latest |
||||
|
framework: netcoreapp3.1 |
||||
|
runtime: -x64 |
||||
|
codecov: true |
||||
|
|
||||
|
runs-on: ${{matrix.options.os}} |
||||
|
|
||||
|
steps: |
||||
|
- name: Git Config |
||||
|
shell: bash |
||||
|
run: | |
||||
|
git config --global core.autocrlf false |
||||
|
git config --global core.longpaths true |
||||
|
|
||||
|
- name: Git Checkout |
||||
|
uses: actions/checkout@v2 |
||||
|
with: |
||||
|
fetch-depth: 0 |
||||
|
submodules: recursive |
||||
|
|
||||
|
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 |
||||
|
- name: Git Create LFS FileList |
||||
|
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id |
||||
|
|
||||
|
- name: Git Setup LFS Cache |
||||
|
uses: actions/cache@v2 |
||||
|
id: lfs-cache |
||||
|
with: |
||||
|
path: .git/lfs |
||||
|
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 |
||||
|
|
||||
|
- name: Git Pull LFS |
||||
|
run: git lfs pull |
||||
|
|
||||
|
- name: NuGet Install |
||||
|
uses: NuGet/setup-nuget@v1 |
||||
|
|
||||
|
- name: NuGet Setup Cache |
||||
|
uses: actions/cache@v2 |
||||
|
id: nuget-cache |
||||
|
with: |
||||
|
path: ~/.nuget |
||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} |
||||
|
restore-keys: ${{ runner.os }}-nuget- |
||||
|
|
||||
|
- name: DotNet Build |
||||
|
shell: pwsh |
||||
|
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
||||
|
env: |
||||
|
SIXLABORS_TESTING: True |
||||
|
|
||||
|
- name: DotNet Test |
||||
|
shell: pwsh |
||||
|
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
||||
|
env: |
||||
|
SIXLABORS_TESTING: True |
||||
|
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit |
||||
|
|
||||
|
- name: Export Failed Output |
||||
|
uses: actions/upload-artifact@v2 |
||||
|
if: failure() |
||||
|
with: |
||||
|
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip |
||||
|
path: tests/Images/ActualOutput/ |
||||
|
|
||||
|
- name: Codecov Update |
||||
|
uses: codecov/codecov-action@v1 |
||||
|
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') |
||||
|
with: |
||||
|
flags: unittests |
||||
@ -1 +1 @@ |
|||||
Subproject commit 33cb12ca77f919b44de56f344d2627cc2a108c3a |
Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 |
||||
@ -0,0 +1,98 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Common.Helpers |
||||
|
{ |
||||
|
internal static class HexConverter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character.
|
||||
|
/// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991.
|
||||
|
/// </summary>
|
||||
|
/// <param name="chars">The hexadecimal string to parse.</param>
|
||||
|
/// <param name="bytes">The destination for the parsed bytes. Must be at least <paramref name="chars"/>.Length / 2 bytes long.</param>
|
||||
|
/// <returns>The number of bytes written to <paramref name="bytes"/>.</returns>
|
||||
|
public static int HexStringToBytes(ReadOnlySpan<char> chars, Span<byte> bytes) |
||||
|
{ |
||||
|
if ((chars.Length % 2) != 0) |
||||
|
{ |
||||
|
throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); |
||||
|
} |
||||
|
|
||||
|
if ((bytes.Length * 2) < chars.Length) |
||||
|
{ |
||||
|
throw new ArgumentException("Output span must be at least half the length of the input string"); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Slightly better performance in the loop below, allows us to skip a bounds check
|
||||
|
// while still supporting output buffers that are larger than necessary
|
||||
|
bytes = bytes.Slice(0, chars.Length / 2); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
static int FromChar(int c) |
||||
|
{ |
||||
|
// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.
|
||||
|
// This doesn't actually allocate.
|
||||
|
ReadOnlySpan<byte> charToHexLookup = new byte[] |
||||
|
{ |
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
|
||||
|
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
|
||||
|
0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
|
||||
|
0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
|
||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255
|
||||
|
}; |
||||
|
|
||||
|
return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; |
||||
|
} |
||||
|
|
||||
|
// See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361
|
||||
|
int i = 0; |
||||
|
int j = 0; |
||||
|
int byteLo = 0; |
||||
|
int byteHi = 0; |
||||
|
while (j < bytes.Length) |
||||
|
{ |
||||
|
byteLo = FromChar(chars[i + 1]); |
||||
|
byteHi = FromChar(chars[i]); |
||||
|
|
||||
|
// byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern
|
||||
|
// is if either byteHi or byteLo was not a hex character.
|
||||
|
if ((byteLo | byteHi) == 0xFF) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
bytes[j++] = (byte)((byteHi << 4) | byteLo); |
||||
|
i += 2; |
||||
|
} |
||||
|
|
||||
|
if (byteLo == 0xFF) |
||||
|
{ |
||||
|
i++; |
||||
|
} |
||||
|
|
||||
|
if ((byteLo | byteHi) == 0xFF) |
||||
|
{ |
||||
|
throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); |
||||
|
} |
||||
|
|
||||
|
return j; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,82 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Tuples |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Its faster to process multiple Vector4-s together, so let's pair them!
|
|
||||
/// On AVX2 this pair should be convertible to <see cref="Vector{T}"/> of <see cref="float"/>!
|
|
||||
/// TODO: Investigate defining this as union with an Octet.OfSingle type.
|
|
||||
/// </summary>
|
|
||||
[StructLayout(LayoutKind.Sequential)] |
|
||||
internal struct Vector4Pair |
|
||||
{ |
|
||||
public Vector4 A; |
|
||||
|
|
||||
public Vector4 B; |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public void MultiplyInplace(float value) |
|
||||
{ |
|
||||
this.A *= value; |
|
||||
this.B *= value; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public void AddInplace(Vector4 value) |
|
||||
{ |
|
||||
this.A += value; |
|
||||
this.B += value; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public void AddInplace(ref Vector4Pair other) |
|
||||
{ |
|
||||
this.A += other.A; |
|
||||
this.B += other.B; |
|
||||
} |
|
||||
|
|
||||
/// <summary>.
|
|
||||
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else.
|
|
||||
/// </summary>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
internal void RoundAndDownscalePreVector8(float downscaleFactor) |
|
||||
{ |
|
||||
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A); |
|
||||
a = a.FastRound(); |
|
||||
|
|
||||
ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B); |
|
||||
b = b.FastRound(); |
|
||||
|
|
||||
// Downscale by 1/factor
|
|
||||
var scale = new Vector4(1 / downscaleFactor); |
|
||||
this.A *= scale; |
|
||||
this.B *= scale; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// AVX2-only Downscale method, specific to Jpeg color conversion.
|
|
||||
/// TODO: Move it somewhere else.
|
|
||||
/// </summary>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
internal void RoundAndDownscaleVector8(float downscaleFactor) |
|
||||
{ |
|
||||
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this); |
|
||||
Vector<float> v = self; |
|
||||
v = v.FastRound(); |
|
||||
|
|
||||
// Downscale by 1/factor
|
|
||||
v *= new Vector<float>(1 / downscaleFactor); |
|
||||
self = v; |
|
||||
} |
|
||||
|
|
||||
public override string ToString() |
|
||||
{ |
|
||||
return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,97 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Xmp; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Gif |
||||
|
{ |
||||
|
internal readonly struct GifXmpApplicationExtension : IGifExtension |
||||
|
{ |
||||
|
public GifXmpApplicationExtension(byte[] data) => this.Data = data; |
||||
|
|
||||
|
public byte Label => GifConstants.ApplicationExtensionLabel; |
||||
|
|
||||
|
public int ContentLength => this.Data.Length + 269; // 12 + Data Length + 1 + 256
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the raw Data.
|
||||
|
/// </summary>
|
||||
|
public byte[] Data { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads the XMP metadata from the specified stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream to read from.</param>
|
||||
|
/// <returns>The XMP metadata</returns>
|
||||
|
/// <exception cref="ImageFormatException">Thrown if the XMP block is not properly terminated.</exception>
|
||||
|
public static GifXmpApplicationExtension Read(Stream stream) |
||||
|
{ |
||||
|
// Read data in blocks, until an \0 character is encountered.
|
||||
|
// We overshoot, indicated by the terminatorIndex variable.
|
||||
|
const int bufferSize = 256; |
||||
|
var list = new List<byte[]>(); |
||||
|
int terminationIndex = -1; |
||||
|
while (terminationIndex < 0) |
||||
|
{ |
||||
|
byte[] temp = new byte[bufferSize]; |
||||
|
int bytesRead = stream.Read(temp); |
||||
|
list.Add(temp); |
||||
|
terminationIndex = Array.IndexOf(temp, (byte)1); |
||||
|
} |
||||
|
|
||||
|
// Pack all the blocks (except magic trailer) into one single array again.
|
||||
|
int dataSize = ((list.Count - 1) * bufferSize) + terminationIndex; |
||||
|
byte[] buffer = new byte[dataSize]; |
||||
|
Span<byte> bufferSpan = buffer; |
||||
|
int pos = 0; |
||||
|
for (int j = 0; j < list.Count - 1; j++) |
||||
|
{ |
||||
|
list[j].CopyTo(bufferSpan.Slice(pos)); |
||||
|
pos += bufferSize; |
||||
|
} |
||||
|
|
||||
|
// Last one only needs the portion until terminationIndex copied over.
|
||||
|
Span<byte> lastBytes = list[list.Count - 1]; |
||||
|
lastBytes.Slice(0, terminationIndex).CopyTo(bufferSpan.Slice(pos)); |
||||
|
|
||||
|
// Skip the remainder of the magic trailer.
|
||||
|
stream.Skip(258 - (bufferSize - terminationIndex)); |
||||
|
return new GifXmpApplicationExtension(buffer); |
||||
|
} |
||||
|
|
||||
|
public int WriteTo(Span<byte> buffer) |
||||
|
{ |
||||
|
int totalSize = this.ContentLength; |
||||
|
if (buffer.Length < totalSize) |
||||
|
{ |
||||
|
throw new InsufficientMemoryException("Unable to write XMP metadata to GIF image"); |
||||
|
} |
||||
|
|
||||
|
int bytesWritten = 0; |
||||
|
buffer[bytesWritten++] = GifConstants.ApplicationBlockSize; |
||||
|
|
||||
|
// Write "XMP DataXMP"
|
||||
|
ReadOnlySpan<byte> idBytes = GifConstants.XmpApplicationIdentificationBytes; |
||||
|
idBytes.CopyTo(buffer.Slice(bytesWritten)); |
||||
|
bytesWritten += idBytes.Length; |
||||
|
|
||||
|
// XMP Data itself
|
||||
|
this.Data.CopyTo(buffer.Slice(bytesWritten)); |
||||
|
bytesWritten += this.Data.Length; |
||||
|
|
||||
|
// Write the Magic Trailer
|
||||
|
buffer[bytesWritten++] = 0x01; |
||||
|
for (byte i = 255; i > 0; i--) |
||||
|
{ |
||||
|
buffer[bytesWritten++] = i; |
||||
|
} |
||||
|
|
||||
|
buffer[bytesWritten++] = 0x00; |
||||
|
|
||||
|
return totalSize; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
||||
|
{ |
||||
|
internal unsafe partial struct Block8x8 |
||||
|
{ |
||||
|
[FieldOffset(0)] |
||||
|
public Vector128<short> V0; |
||||
|
[FieldOffset(16)] |
||||
|
public Vector128<short> V1; |
||||
|
[FieldOffset(32)] |
||||
|
public Vector128<short> V2; |
||||
|
[FieldOffset(48)] |
||||
|
public Vector128<short> V3; |
||||
|
[FieldOffset(64)] |
||||
|
public Vector128<short> V4; |
||||
|
[FieldOffset(80)] |
||||
|
public Vector128<short> V5; |
||||
|
[FieldOffset(96)] |
||||
|
public Vector128<short> V6; |
||||
|
[FieldOffset(112)] |
||||
|
public Vector128<short> V7; |
||||
|
|
||||
|
[FieldOffset(0)] |
||||
|
public Vector256<short> V01; |
||||
|
[FieldOffset(32)] |
||||
|
public Vector256<short> V23; |
||||
|
[FieldOffset(64)] |
||||
|
public Vector256<short> V45; |
||||
|
[FieldOffset(96)] |
||||
|
public Vector256<short> V67; |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -1,18 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter |
|
||||
{ |
|
||||
protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision, 8) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected sealed override bool IsAvailable => SimdUtils.HasAvx2; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal abstract class BasicJpegColorConverter : JpegColorConverter |
|
||||
{ |
|
||||
protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override bool IsAvailable => true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,52 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
internal sealed class FromCmykAvx : JpegColorConverterAvx |
||||
|
{ |
||||
|
public FromCmykAvx(int precision) |
||||
|
: base(JpegColorSpace.Cmyk, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void ConvertToRgbInplace(in ComponentValues values) |
||||
|
{ |
||||
|
ref Vector256<float> c0Base = |
||||
|
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
||||
|
ref Vector256<float> c1Base = |
||||
|
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
||||
|
ref Vector256<float> c2Base = |
||||
|
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
||||
|
ref Vector256<float> c3Base = |
||||
|
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
||||
|
|
||||
|
// Used for the color conversion
|
||||
|
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); |
||||
|
|
||||
|
nint n = values.Component0.Length / Vector256<float>.Count; |
||||
|
for (nint i = 0; i < n; i++) |
||||
|
{ |
||||
|
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i); |
||||
|
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i); |
||||
|
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i); |
||||
|
Vector256<float> k = Unsafe.Add(ref c3Base, i); |
||||
|
|
||||
|
k = Avx.Multiply(k, scale); |
||||
|
c = Avx.Multiply(c, k); |
||||
|
m = Avx.Multiply(m, k); |
||||
|
y = Avx.Multiply(y, k); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -1,60 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// 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; |
|
||||
using static SixLabors.ImageSharp.SimdUtils; |
|
||||
#endif
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal sealed class FromCmykAvx2 : Avx2JpegColorConverter |
|
||||
{ |
|
||||
public FromCmykAvx2(int precision) |
|
||||
: base(JpegColorSpace.Cmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|
||||
{ |
|
||||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|
||||
ref Vector256<float> c0Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector256<float> c1Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector256<float> c2Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector256<float> c3Base = |
|
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
// Used for the color conversion
|
|
||||
var scale = Vector256.Create(1 / this.MaximumValue); |
|
||||
|
|
||||
nint n = values.Component0.Length / 8; |
|
||||
for (nint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i); |
|
||||
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i); |
|
||||
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i); |
|
||||
Vector256<float> k = Unsafe.Add(ref c3Base, i); |
|
||||
|
|
||||
k = Avx.Multiply(k, scale); |
|
||||
c = Avx.Multiply(Avx.Multiply(c, k), scale); |
|
||||
m = Avx.Multiply(Avx.Multiply(m, k), scale); |
|
||||
y = Avx.Multiply(Avx.Multiply(y, k), scale); |
|
||||
} |
|
||||
#endif
|
|
||||
} |
|
||||
|
|
||||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|
||||
FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,51 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
internal sealed class FromCmykVector : JpegColorConverterVector |
||||
|
{ |
||||
|
public FromCmykVector(int precision) |
||||
|
: base(JpegColorSpace.Cmyk, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
||||
|
{ |
||||
|
ref Vector<float> cBase = |
||||
|
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
||||
|
ref Vector<float> mBase = |
||||
|
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
||||
|
ref Vector<float> yBase = |
||||
|
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
||||
|
ref Vector<float> kBase = |
||||
|
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
||||
|
|
||||
|
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue)); |
||||
|
|
||||
|
nint n = values.Component0.Length / Vector<float>.Count; |
||||
|
for (nint i = 0; i < n; i++) |
||||
|
{ |
||||
|
ref Vector<float> c = ref Unsafe.Add(ref cBase, i); |
||||
|
ref Vector<float> m = ref Unsafe.Add(ref mBase, i); |
||||
|
ref Vector<float> y = ref Unsafe.Add(ref yBase, i); |
||||
|
Vector<float> k = Unsafe.Add(ref kBase, i); |
||||
|
|
||||
|
k *= scale; |
||||
|
c *= k; |
||||
|
m *= k; |
||||
|
y *= k; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void ConvertCoreInplace(in ComponentValues values) => |
||||
|
FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,53 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Tuples; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal sealed class FromCmykVector8 : Vector8JpegColorConverter |
|
||||
{ |
|
||||
public FromCmykVector8(int precision) |
|
||||
: base(JpegColorSpace.Cmyk, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|
||||
{ |
|
||||
ref Vector<float> cBase = |
|
||||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector<float> mBase = |
|
||||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector<float> yBase = |
|
||||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
ref Vector<float> kBase = |
|
||||
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); |
|
||||
|
|
||||
var scale = new Vector<float>(1 / this.MaximumValue); |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nint n = values.Component0.Length / 8; |
|
||||
for (nint i = 0; i < n; i++) |
|
||||
{ |
|
||||
ref Vector<float> c = ref Unsafe.Add(ref cBase, i); |
|
||||
ref Vector<float> m = ref Unsafe.Add(ref mBase, i); |
|
||||
ref Vector<float> y = ref Unsafe.Add(ref yBase, i); |
|
||||
Vector<float> k = Unsafe.Add(ref kBase, i) * scale; |
|
||||
|
|
||||
c = (c * k) * scale; |
|
||||
m = (m * k) * scale; |
|
||||
y = (y * k) * scale; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|
||||
FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,47 +1,39 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
using System.Runtime.CompilerServices; |
||||
using System.Runtime.InteropServices; |
using System.Runtime.InteropServices; |
||||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|
||||
using System.Runtime.Intrinsics; |
using System.Runtime.Intrinsics; |
||||
using System.Runtime.Intrinsics.X86; |
using System.Runtime.Intrinsics.X86; |
||||
using static SixLabors.ImageSharp.SimdUtils; |
|
||||
#endif
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
{ |
{ |
||||
internal abstract partial class JpegColorConverter |
internal abstract partial class JpegColorConverterBase |
||||
{ |
{ |
||||
internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter |
internal sealed class FromGrayscaleAvx : JpegColorConverterAvx |
||||
{ |
{ |
||||
public FromGrayscaleAvx2(int precision) |
public FromGrayscaleAvx(int precision) |
||||
: base(JpegColorSpace.Grayscale, precision) |
: base(JpegColorSpace.Grayscale, precision) |
||||
{ |
{ |
||||
} |
} |
||||
|
|
||||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
public override void ConvertToRgbInplace(in ComponentValues values) |
||||
{ |
{ |
||||
#if SUPPORTS_RUNTIME_INTRINSICS
|
|
||||
ref Vector256<float> c0Base = |
ref Vector256<float> c0Base = |
||||
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
||||
|
|
||||
// Used for the color conversion
|
// Used for the color conversion
|
||||
var scale = Vector256.Create(1 / this.MaximumValue); |
var scale = Vector256.Create(1 / this.MaximumValue); |
||||
|
|
||||
nint n = values.Component0.Length / 8; |
nint n = values.Component0.Length / Vector256<float>.Count; |
||||
for (nint i = 0; i < n; i++) |
for (nint i = 0; i < n; i++) |
||||
{ |
{ |
||||
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i); |
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i); |
||||
c0 = Avx.Multiply(c0, scale); |
c0 = Avx.Multiply(c0, scale); |
||||
} |
} |
||||
#endif
|
|
||||
} |
} |
||||
|
|
||||
protected override void ConvertCoreInplace(in ComponentValues values) => |
|
||||
FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
#endif
|
||||
@ -1,53 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal sealed class FromGrayscaleBasic : BasicJpegColorConverter |
|
||||
{ |
|
||||
public FromGrayscaleBasic(int precision) |
|
||||
: base(JpegColorSpace.Grayscale, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override void ConvertToRgbInplace(in ComponentValues values) => |
|
||||
ScaleValues(values.Component0, this.MaximumValue); |
|
||||
|
|
||||
internal static void ScaleValues(Span<float> values, float maxValue) |
|
||||
{ |
|
||||
Span<Vector4> vecValues = MemoryMarshal.Cast<float, Vector4>(values); |
|
||||
|
|
||||
var scaleVector = new Vector4(1 / maxValue); |
|
||||
|
|
||||
for (int i = 0; i < vecValues.Length; i++) |
|
||||
{ |
|
||||
vecValues[i] *= scaleVector; |
|
||||
} |
|
||||
|
|
||||
values = values.Slice(vecValues.Length * 4); |
|
||||
if (!values.IsEmpty) |
|
||||
{ |
|
||||
float scaleValue = 1f / maxValue; |
|
||||
values[0] *= scaleValue; |
|
||||
|
|
||||
if ((uint)values.Length > 1) |
|
||||
{ |
|
||||
values[1] *= scaleValue; |
|
||||
|
|
||||
if ((uint)values.Length > 2) |
|
||||
{ |
|
||||
values[2] *= scaleValue; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,34 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
internal sealed class FromGrayscaleScalar : JpegColorConverterScalar |
||||
|
{ |
||||
|
public FromGrayscaleScalar(int precision) |
||||
|
: base(JpegColorSpace.Grayscale, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void ConvertToRgbInplace(in ComponentValues values) => |
||||
|
ConvertCoreInplace(values.Component0, this.MaximumValue); |
||||
|
|
||||
|
internal static void ConvertCoreInplace(Span<float> values, float maxValue) |
||||
|
{ |
||||
|
ref float valuesRef = ref MemoryMarshal.GetReference(values); |
||||
|
float scale = 1 / maxValue; |
||||
|
|
||||
|
for (nint i = 0; i < values.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref valuesRef, i) *= scale; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
internal sealed class FromGrayScaleVector : JpegColorConverterVector |
||||
|
{ |
||||
|
public FromGrayScaleVector(int precision) |
||||
|
: base(JpegColorSpace.Grayscale, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
||||
|
{ |
||||
|
ref Vector<float> cBase = |
||||
|
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); |
||||
|
|
||||
|
var scale = new Vector<float>(1 / this.MaximumValue); |
||||
|
|
||||
|
nint n = values.Component0.Length / Vector<float>.Count; |
||||
|
for (nint i = 0; i < n; i++) |
||||
|
{ |
||||
|
ref Vector<float> c0 = ref Unsafe.Add(ref cBase, i); |
||||
|
c0 *= scale; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void ConvertCoreInplace(in ComponentValues values) => |
||||
|
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,32 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal sealed class FromRgbBasic : BasicJpegColorConverter |
|
||||
{ |
|
||||
public FromRgbBasic(int precision) |
|
||||
: base(JpegColorSpace.RGB, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override void ConvertToRgbInplace(in ComponentValues values) |
|
||||
{ |
|
||||
ConvertCoreInplace(values, this.MaximumValue); |
|
||||
} |
|
||||
|
|
||||
internal static void ConvertCoreInplace(ComponentValues values, float maxValue) |
|
||||
{ |
|
||||
FromGrayscaleBasic.ScaleValues(values.Component0, maxValue); |
|
||||
FromGrayscaleBasic.ScaleValues(values.Component1, maxValue); |
|
||||
FromGrayscaleBasic.ScaleValues(values.Component2, maxValue); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
internal sealed class FromRgbScalar : JpegColorConverterScalar |
||||
|
{ |
||||
|
public FromRgbScalar(int precision) |
||||
|
: base(JpegColorSpace.RGB, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void ConvertToRgbInplace(in ComponentValues values) => |
||||
|
ConvertCoreInplace(values, this.MaximumValue); |
||||
|
|
||||
|
internal static void ConvertCoreInplace(ComponentValues values, float maxValue) |
||||
|
{ |
||||
|
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); |
||||
|
FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); |
||||
|
FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,42 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal sealed class FromYCbCrBasic : BasicJpegColorConverter |
|
||||
{ |
|
||||
public FromYCbCrBasic(int precision) |
|
||||
: base(JpegColorSpace.YCbCr, precision) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override void ConvertToRgbInplace(in ComponentValues values) |
|
||||
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); |
|
||||
|
|
||||
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) |
|
||||
{ |
|
||||
Span<float> c0 = values.Component0; |
|
||||
Span<float> c1 = values.Component1; |
|
||||
Span<float> c2 = values.Component2; |
|
||||
|
|
||||
var scale = 1 / maxValue; |
|
||||
|
|
||||
for (int i = 0; i < c0.Length; i++) |
|
||||
{ |
|
||||
float y = c0[i]; |
|
||||
float cb = c1[i] - halfValue; |
|
||||
float cr = c2[i] - halfValue; |
|
||||
|
|
||||
c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale; |
|
||||
c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale; |
|
||||
c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,50 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
internal sealed class FromYCbCrScalar : JpegColorConverterScalar |
||||
|
{ |
||||
|
// TODO: comments, derived from ITU-T Rec. T.871
|
||||
|
internal const float RCrMult = 1.402f; |
||||
|
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); |
||||
|
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); |
||||
|
internal const float BCbMult = 1.772f; |
||||
|
|
||||
|
public FromYCbCrScalar(int precision) |
||||
|
: base(JpegColorSpace.YCbCr, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void ConvertToRgbInplace(in ComponentValues values) |
||||
|
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); |
||||
|
|
||||
|
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) |
||||
|
{ |
||||
|
Span<float> c0 = values.Component0; |
||||
|
Span<float> c1 = values.Component1; |
||||
|
Span<float> c2 = values.Component2; |
||||
|
|
||||
|
float scale = 1 / maxValue; |
||||
|
|
||||
|
for (int i = 0; i < c0.Length; i++) |
||||
|
{ |
||||
|
float y = c0[i]; |
||||
|
float cb = c1[i] - halfValue; |
||||
|
float cr = c2[i] - halfValue; |
||||
|
|
||||
|
// r = y + (1.402F * cr);
|
||||
|
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
||||
|
// b = y + (1.772F * cb);
|
||||
|
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
||||
|
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
||||
|
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,88 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Tuples; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal sealed class FromYCbCrVector4 : VectorizedJpegColorConverter |
|
||||
{ |
|
||||
public FromYCbCrVector4(int precision) |
|
||||
: base(JpegColorSpace.YCbCr, precision, 8) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override bool IsAvailable => SimdUtils.HasVector4; |
|
||||
|
|
||||
protected override void ConvertCoreVectorizedInplace(in ComponentValues values) |
|
||||
{ |
|
||||
DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!"); |
|
||||
|
|
||||
ref Vector4Pair c0Base = |
|
||||
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0)); |
|
||||
ref Vector4Pair c1Base = |
|
||||
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1)); |
|
||||
ref Vector4Pair c2Base = |
|
||||
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2)); |
|
||||
|
|
||||
var chromaOffset = new Vector4(-this.HalfValue); |
|
||||
var maxValue = this.MaximumValue; |
|
||||
|
|
||||
// Walking 8 elements at one step:
|
|
||||
nint n = values.Component0.Length / 8; |
|
||||
|
|
||||
for (nint i = 0; i < n; i++) |
|
||||
{ |
|
||||
// y = yVals[i];
|
|
||||
ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i); |
|
||||
|
|
||||
// cb = cbVals[i] - halfValue);
|
|
||||
ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i); |
|
||||
c1.AddInplace(chromaOffset); |
|
||||
|
|
||||
// cr = crVals[i] - halfValue;
|
|
||||
ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i); |
|
||||
c2.AddInplace(chromaOffset); |
|
||||
|
|
||||
// r = y + (1.402F * cr);
|
|
||||
Vector4Pair r = c0; |
|
||||
Vector4Pair tmp = c2; |
|
||||
tmp.MultiplyInplace(1.402F); |
|
||||
r.AddInplace(ref tmp); |
|
||||
|
|
||||
// g = y - (0.344136F * cb) - (0.714136F * cr);
|
|
||||
Vector4Pair g = c0; |
|
||||
tmp = c1; |
|
||||
tmp.MultiplyInplace(-0.344136F); |
|
||||
g.AddInplace(ref tmp); |
|
||||
tmp = c2; |
|
||||
tmp.MultiplyInplace(-0.714136F); |
|
||||
g.AddInplace(ref tmp); |
|
||||
|
|
||||
// b = y + (1.772F * cb);
|
|
||||
Vector4Pair b = c0; |
|
||||
tmp = c1; |
|
||||
tmp.MultiplyInplace(1.772F); |
|
||||
b.AddInplace(ref tmp); |
|
||||
|
|
||||
r.RoundAndDownscalePreVector8(maxValue); |
|
||||
g.RoundAndDownscalePreVector8(maxValue); |
|
||||
b.RoundAndDownscalePreVector8(maxValue); |
|
||||
|
|
||||
c0 = r; |
|
||||
c1 = g; |
|
||||
c2 = b; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected override void ConvertCoreInplace(in ComponentValues values) |
|
||||
=> FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal abstract class Vector8JpegColorConverter : VectorizedJpegColorConverter |
|
||||
{ |
|
||||
protected Vector8JpegColorConverter(JpegColorSpace colorSpace, int precision) |
|
||||
: base(colorSpace, precision, 8) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected sealed override bool IsAvailable => SimdUtils.HasVector8; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,47 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Numerics; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
|
||||
{ |
|
||||
internal abstract partial class JpegColorConverter |
|
||||
{ |
|
||||
internal abstract class VectorizedJpegColorConverter : JpegColorConverter |
|
||||
{ |
|
||||
private readonly int vectorSize; |
|
||||
|
|
||||
protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision, int vectorSize) |
|
||||
: base(colorSpace, precision) |
|
||||
{ |
|
||||
this.vectorSize = vectorSize; |
|
||||
} |
|
||||
|
|
||||
public override void ConvertToRgbInplace(in ComponentValues values) |
|
||||
{ |
|
||||
int length = values.Component0.Length; |
|
||||
int remainder = values.Component0.Length % this.vectorSize; |
|
||||
int simdCount = length - remainder; |
|
||||
if (simdCount > 0) |
|
||||
{ |
|
||||
// This implementation is actually AVX specific.
|
|
||||
// An AVX register is capable of storing 8 float-s.
|
|
||||
if (!this.IsAvailable) |
|
||||
{ |
|
||||
throw new InvalidOperationException( |
|
||||
"This converter can be used only on architecture having 256 byte floating point SIMD registers!"); |
|
||||
} |
|
||||
|
|
||||
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); |
|
||||
} |
|
||||
|
|
||||
this.ConvertCoreInplace(values.Slice(simdCount, remainder)); |
|
||||
} |
|
||||
|
|
||||
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); |
|
||||
|
|
||||
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,32 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
#if SUPPORTS_RUNTIME_INTRINSICS
|
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
||||
|
/// based on <see cref="Avx"/> instructions.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Converters of this family would expect input buffers lengths to be
|
||||
|
/// divisible by 8 without a remainder.
|
||||
|
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
|
||||
|
/// DO NOT pass test data of invalid size to these converters as they
|
||||
|
/// potentially won't do a bound check and return a false positive result.
|
||||
|
/// </remarks>
|
||||
|
internal abstract class JpegColorConverterAvx : JpegColorConverterBase |
||||
|
{ |
||||
|
protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) |
||||
|
: base(colorSpace, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override bool IsAvailable => Avx.IsSupported; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
||||
|
/// based on scalar instructions.
|
||||
|
/// </summary>
|
||||
|
internal abstract class JpegColorConverterScalar : JpegColorConverterBase |
||||
|
{ |
||||
|
protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) |
||||
|
: base(colorSpace, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override bool IsAvailable => true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters |
||||
|
{ |
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
|
||||
|
/// based on <see cref="Vector"/> API.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Converters of this family can work with data of any size.
|
||||
|
/// Even though real life data is guaranteed to be of size
|
||||
|
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
|
||||
|
/// such data out of the box. These converters have fallback code
|
||||
|
/// for 'remainder' data.
|
||||
|
/// </remarks>
|
||||
|
internal abstract class JpegColorConverterVector : JpegColorConverterBase |
||||
|
{ |
||||
|
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) |
||||
|
: base(colorSpace, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0; |
||||
|
|
||||
|
public override void ConvertToRgbInplace(in ComponentValues values) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); |
||||
|
|
||||
|
int length = values.Component0.Length; |
||||
|
int remainder = (int)((uint)length % (uint)Vector<float>.Count); |
||||
|
|
||||
|
// Jpeg images are guaranteed to have pixel strides at least 8 pixels wide
|
||||
|
// Thus there's no need to check whether simdCount is greater than zero
|
||||
|
int simdCount = length - remainder; |
||||
|
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); |
||||
|
|
||||
|
// Jpeg images width is always divisible by 8 without a remainder
|
||||
|
// so it's safe to say SSE/AVX implementations would never have
|
||||
|
// 'remainder' pixels
|
||||
|
// But some exotic simd implementations e.g. AVX-512 can have
|
||||
|
// remainder pixels
|
||||
|
if (remainder > 0) |
||||
|
{ |
||||
|
this.ConvertCoreInplace(values.Slice(simdCount, remainder)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); |
||||
|
|
||||
|
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,88 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels.
|
|
||||
/// </summary>
|
|
||||
[StructLayout(LayoutKind.Sequential)] |
|
||||
internal struct JpegBlockPostProcessor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Source block
|
|
||||
/// </summary>
|
|
||||
public Block8x8F SourceBlock; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Temporal block to store intermediate computation results.
|
|
||||
/// </summary>
|
|
||||
public Block8x8F WorkspaceBlock; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The quantization table as <see cref="Block8x8F"/>.
|
|
||||
/// </summary>
|
|
||||
public Block8x8F DequantiazationTable; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
|
|
||||
/// </summary>
|
|
||||
private Size subSamplingDivisors; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="decoder">The raw jpeg data.</param>
|
|
||||
/// <param name="component">The raw component.</param>
|
|
||||
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) |
|
||||
{ |
|
||||
int qtIndex = component.QuantizationTableIndex; |
|
||||
this.DequantiazationTable = decoder.QuantizationTables[qtIndex]; |
|
||||
this.subSamplingDivisors = component.SubSamplingDivisors; |
|
||||
|
|
||||
this.SourceBlock = default; |
|
||||
this.WorkspaceBlock = default; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
|
|
||||
/// - Dequantize
|
|
||||
/// - Applying IDCT
|
|
||||
/// - Level shift by +maximumValue/2, clip to [0, maximumValue]
|
|
||||
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="sourceBlock">The source block.</param>
|
|
||||
/// <param name="destAreaOrigin">Reference to the origin of the destination pixel area.</param>
|
|
||||
/// <param name="destAreaStride">The width of the destination pixel buffer.</param>
|
|
||||
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
|
|
||||
public void ProcessBlockColorsInto( |
|
||||
ref Block8x8 sourceBlock, |
|
||||
ref float destAreaOrigin, |
|
||||
int destAreaStride, |
|
||||
float maximumValue) |
|
||||
{ |
|
||||
ref Block8x8F block = ref this.SourceBlock; |
|
||||
block.LoadFrom(ref sourceBlock); |
|
||||
|
|
||||
// Dequantize:
|
|
||||
block.MultiplyInPlace(ref this.DequantiazationTable); |
|
||||
|
|
||||
FastFloatingPointDCT.TransformIDCT(ref block, ref this.WorkspaceBlock); |
|
||||
|
|
||||
// To conform better to libjpeg we actually NEED TO loose precision here.
|
|
||||
// This is because they store blocks as Int16 between all the operations.
|
|
||||
// To be "more accurate", we need to emulate this by rounding!
|
|
||||
block.NormalizeColorsAndRoundInPlace(maximumValue); |
|
||||
|
|
||||
block.ScaledCopyTo( |
|
||||
ref destAreaOrigin, |
|
||||
destAreaStride, |
|
||||
this.subSamplingDivisors.Width, |
|
||||
this.subSamplingDivisors.Height); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,23 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
// <auto-generated />
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|
||||
{ |
|
||||
internal unsafe partial struct GenericBlock8x8<T> |
|
||||
{ |
|
||||
#pragma warning disable 169
|
|
||||
|
|
||||
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
|
|
||||
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; |
|
||||
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; |
|
||||
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; |
|
||||
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; |
|
||||
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; |
|
||||
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; |
|
||||
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; |
|
||||
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; |
|
||||
|
|
||||
#pragma warning restore 169
|
|
||||
} |
|
||||
} |
|
||||
@ -1,122 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
// ReSharper disable InconsistentNaming
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
|
|
||||
/// </summary>
|
|
||||
[StructLayout(LayoutKind.Sequential)] |
|
||||
internal unsafe partial struct GenericBlock8x8<T> |
|
||||
where T : unmanaged |
|
||||
{ |
|
||||
public const int Size = 64; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// FOR TESTING ONLY!
|
|
||||
/// Gets or sets a <see cref="Rgb24"/> value at the given index
|
|
||||
/// </summary>
|
|
||||
/// <param name="idx">The index</param>
|
|
||||
/// <returns>The value</returns>
|
|
||||
public T this[int idx] |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
|
||||
return Unsafe.Add(ref selfRef, idx); |
|
||||
} |
|
||||
|
|
||||
set |
|
||||
{ |
|
||||
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
|
||||
Unsafe.Add(ref selfRef, idx) = value; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// FOR TESTING ONLY!
|
|
||||
/// Gets or sets a value in a row+column of the 8x8 block
|
|
||||
/// </summary>
|
|
||||
/// <param name="x">The x position index in the row</param>
|
|
||||
/// <param name="y">The column index</param>
|
|
||||
/// <returns>The value</returns>
|
|
||||
public T this[int x, int y] |
|
||||
{ |
|
||||
get => this[(y * 8) + x]; |
|
||||
set => this[(y * 8) + x] = value; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 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.
|
|
||||
/// </summary>
|
|
||||
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, ref RowOctet<T> currentRows) |
|
||||
{ |
|
||||
int width = Math.Min(8, source.Width - sourceX); |
|
||||
int height = Math.Min(8, source.Height - sourceY); |
|
||||
|
|
||||
if (width <= 0 || height <= 0) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>(); |
|
||||
int remainderXCount = 8 - width; |
|
||||
|
|
||||
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this); |
|
||||
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>(); |
|
||||
|
|
||||
for (int y = 0; y < height; y++) |
|
||||
{ |
|
||||
Span<T> row = currentRows[y]; |
|
||||
|
|
||||
ref byte s = ref Unsafe.As<T, byte>(ref row[sourceX]); |
|
||||
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); |
|
||||
|
|
||||
Unsafe.CopyBlock(ref d, ref s, byteWidth); |
|
||||
|
|
||||
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1); |
|
||||
|
|
||||
for (int x = 1; x <= remainderXCount; x++) |
|
||||
{ |
|
||||
Unsafe.Add(ref last, x) = last; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
int remainderYCount = 8 - height; |
|
||||
|
|
||||
if (remainderYCount == 0) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); |
|
||||
|
|
||||
for (int y = 1; y <= remainderYCount; y++) |
|
||||
{ |
|
||||
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); |
|
||||
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Only for on-stack instances!
|
|
||||
/// </summary>
|
|
||||
public Span<T> AsSpanUnsafe() |
|
||||
{ |
|
||||
#if SUPPORTS_CREATESPAN
|
|
||||
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1); |
|
||||
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s); |
|
||||
#else
|
|
||||
return new Span<T>(Unsafe.AsPointer(ref this), Size); |
|
||||
#endif
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,194 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixel decoding methods for the PBM binary encoding.
|
||||
|
/// </summary>
|
||||
|
internal class BinaryDecoder |
||||
|
{ |
||||
|
private static L8 white = new(255); |
||||
|
private static L8 black = new(0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decode the specified pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="pixels">The pixel array to encode into.</param>
|
||||
|
/// <param name="stream">The stream to read the data from.</param>
|
||||
|
/// <param name="colorType">The ColorType to decode.</param>
|
||||
|
/// <param name="componentType">Data type of the pixles components.</param>
|
||||
|
/// <exception cref="InvalidImageContentException">
|
||||
|
/// Thrown if an invalid combination of setting is requested.
|
||||
|
/// </exception>
|
||||
|
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
ProcessGrayscale(configuration, pixels, stream); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessWideGrayscale(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
else if (colorType == PbmColorType.Rgb) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
ProcessRgb(configuration, pixels, stream); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessWideRgb(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ProcessBlackAndWhite(configuration, pixels, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 1; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL8Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 2; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL16Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 3; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb24Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 6; |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
stream.Read(rowSpan); |
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb48Bytes( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan, |
||||
|
width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = pixels.Width; |
||||
|
int height = pixels.Height; |
||||
|
int startBit = 0; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width;) |
||||
|
{ |
||||
|
int raw = stream.ReadByte(); |
||||
|
int bit = startBit; |
||||
|
startBit = 0; |
||||
|
for (; bit < 8; bit++) |
||||
|
{ |
||||
|
bool bitValue = (raw & (0x80 >> bit)) != 0; |
||||
|
rowSpan[x] = bitValue ? black : white; |
||||
|
x++; |
||||
|
if (x == width) |
||||
|
{ |
||||
|
startBit = (bit + 1) & 7; // Round off to below 8.
|
||||
|
if (startBit != 0) |
||||
|
{ |
||||
|
stream.Seek(-1, System.IO.SeekOrigin.Current); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromL8( |
||||
|
configuration, |
||||
|
rowSpan, |
||||
|
pixelSpan); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixel encoding methods for the PBM binary encoding.
|
||||
|
/// </summary>
|
||||
|
internal class BinaryEncoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decode pixels into the PBM binary encoding.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="stream">The bytestream to write to.</param>
|
||||
|
/// <param name="image">The input image.</param>
|
||||
|
/// <param name="colorType">The ColorType to use.</param>
|
||||
|
/// <param name="componentType">Data type of the pixles components.</param>
|
||||
|
/// <exception cref="InvalidImageContentException">
|
||||
|
/// Thrown if an invalid combination of setting is requested.
|
||||
|
/// </exception>
|
||||
|
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (colorType == PbmColorType.Grayscale) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
WriteGrayscale(configuration, stream, image); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteWideGrayscale(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
else if (colorType == PbmColorType.Rgb) |
||||
|
{ |
||||
|
if (componentType == PbmComponentType.Byte) |
||||
|
{ |
||||
|
WriteRgb(configuration, stream, image); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteWideRgb(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WriteBlackAndWhite(configuration, stream, image); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToL8Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 2; |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToL16Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 3; |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToRgb24Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
const int bytesPerPixel = 6; |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel); |
||||
|
Span<byte> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToRgb48Bytes( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan, |
||||
|
width); |
||||
|
|
||||
|
stream.Write(rowSpan); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int width = image.Width; |
||||
|
int height = image.Height; |
||||
|
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer; |
||||
|
MemoryAllocator allocator = configuration.MemoryAllocator; |
||||
|
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width); |
||||
|
Span<L8> rowSpan = row.GetSpan(); |
||||
|
|
||||
|
int previousValue = 0; |
||||
|
int startBit = 0; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToL8( |
||||
|
configuration, |
||||
|
pixelSpan, |
||||
|
rowSpan); |
||||
|
|
||||
|
for (int x = 0; x < width;) |
||||
|
{ |
||||
|
int value = previousValue; |
||||
|
for (int i = startBit; i < 8; i++) |
||||
|
{ |
||||
|
if (rowSpan[x].PackedValue < 128) |
||||
|
{ |
||||
|
value |= 0x80 >> i; |
||||
|
} |
||||
|
|
||||
|
x++; |
||||
|
if (x == width) |
||||
|
{ |
||||
|
previousValue = value; |
||||
|
startBit = (i + 1) & 7; // Round off to below 8.
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (startBit == 0) |
||||
|
{ |
||||
|
stream.WriteByte((byte)value); |
||||
|
previousValue = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extensions methods for <see cref="BufferedReadStream"/>.
|
||||
|
/// </summary>
|
||||
|
internal static class BufferedReadStreamExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Skip over any whitespace or any comments.
|
||||
|
/// </summary>
|
||||
|
public static void SkipWhitespaceAndComments(this BufferedReadStream stream) |
||||
|
{ |
||||
|
bool isWhitespace; |
||||
|
do |
||||
|
{ |
||||
|
int val = stream.ReadByte(); |
||||
|
|
||||
|
// Comments start with '#' and end at the next new-line.
|
||||
|
if (val == 0x23) |
||||
|
{ |
||||
|
int innerValue; |
||||
|
do |
||||
|
{ |
||||
|
innerValue = stream.ReadByte(); |
||||
|
} |
||||
|
while (innerValue != 0x0a); |
||||
|
|
||||
|
// Continue searching for whitespace.
|
||||
|
val = innerValue; |
||||
|
} |
||||
|
|
||||
|
isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; |
||||
|
} |
||||
|
while (isWhitespace); |
||||
|
stream.Seek(-1, SeekOrigin.Current); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Read a decimal text value.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The integer value of the decimal.</returns>
|
||||
|
public static int ReadDecimal(this BufferedReadStream stream) |
||||
|
{ |
||||
|
int value = 0; |
||||
|
while (true) |
||||
|
{ |
||||
|
int current = stream.ReadByte() - 0x30; |
||||
|
if ((uint)current > 9) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
value = (value * 10) + current; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Pbm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Configuration options for use during PBM encoding.
|
||||
|
/// </summary>
|
||||
|
internal interface IPbmEncoderOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the encoding of the pixels.
|
||||
|
/// </summary>
|
||||
|
PbmEncoding? Encoding { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Color type of the resulting image.
|
||||
|
/// </summary>
|
||||
|
PbmColorType? ColorType { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Data Type of the pixel components.
|
||||
|
/// </summary>
|
||||
|
PbmComponentType? ComponentType { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Pbm; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class MetadataExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the pbm format specific metadata for the image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="metadata">The metadata this method extends.</param>
|
||||
|
/// <returns>The <see cref="PbmMetadata"/>.</returns>
|
||||
|
public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue