mirror of https://github.com/SixLabors/ImageSharp
200 changed files with 7110 additions and 2423 deletions
@ -1,33 +0,0 @@ |
|||
--- |
|||
name: "Commercial License : Bug Report" |
|||
about: | |
|||
Create a report to help us improve the project. For Commercial License holders only. |
|||
Please contact help@sixlabors.com for issues requiring private support. |
|||
labels: commercial, needs triage |
|||
|
|||
--- |
|||
|
|||
|
|||
### Prerequisites |
|||
|
|||
- [ ] I have written a descriptive issue title |
|||
- [ ] I have verified that I am running the latest version of ImageSharp |
|||
- [ ] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode |
|||
- [ ] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported |
|||
|
|||
### Description |
|||
<!-- A description of the bug or feature --> |
|||
|
|||
### Steps to Reproduce |
|||
<!-- List of steps, sample code, failing test or link to a project that reproduces the behavior --> |
|||
|
|||
### System Configuration |
|||
<!-- Tell us about the environment where you are experiencing the bug --> |
|||
|
|||
- ImageSharp version: |
|||
- Other ImageSharp packages and versions: |
|||
- Environment (Operating system, version and so on): |
|||
- .NET Framework version: |
|||
- Additional information: |
|||
|
|||
<!-- Thanks for reporting the issue to ImageSharp! --> |
|||
@ -0,0 +1,42 @@ |
|||
name: "Commercial License : Bug Report" |
|||
description: | |
|||
Create a report to help us improve the project. For Commercial License holders only. |
|||
Please contact help@sixlabors.com for issues requiring private support. |
|||
labels: ["commercial", "needs triage"] |
|||
body: |
|||
- type: input |
|||
attributes: |
|||
label: ImageSharp version |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: Other ImageSharp packages and versions |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: Environment (Operating system, version and so on) |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: .NET Framework version |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Description |
|||
description: A description of the bug |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Steps to Reproduce |
|||
description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Images |
|||
description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead. |
|||
@ -1,30 +0,0 @@ |
|||
--- |
|||
name: "OSS : Bug Report" |
|||
about: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged. |
|||
labels: needs triage |
|||
|
|||
--- |
|||
|
|||
### Prerequisites |
|||
|
|||
- [ ] I have written a descriptive issue title |
|||
- [ ] I have verified that I am running the latest version of ImageSharp |
|||
- [ ] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode |
|||
- [ ] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported |
|||
|
|||
### Description |
|||
<!-- A description of the bug or feature --> |
|||
|
|||
### Steps to Reproduce |
|||
<!-- List of steps, sample code, failing test or link to a project that reproduces the behavior --> |
|||
|
|||
### System Configuration |
|||
<!-- Tell us about the environment where you are experiencing the bug --> |
|||
|
|||
- ImageSharp version: |
|||
- Other ImageSharp packages and versions: |
|||
- Environment (Operating system, version and so on): |
|||
- .NET Framework version: |
|||
- Additional information: |
|||
|
|||
<!-- Thanks for reporting the issue to ImageSharp! --> |
|||
@ -0,0 +1,40 @@ |
|||
name: "OSS : Bug Report" |
|||
description: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged. |
|||
labels: ["needs triage"] |
|||
body: |
|||
- type: input |
|||
attributes: |
|||
label: ImageSharp version |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: Other ImageSharp packages and versions |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: Environment (Operating system, version and so on) |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: .NET Framework version |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Description |
|||
description: A description of the bug |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Steps to Reproduce |
|||
description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Images |
|||
description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead. |
|||
@ -1,202 +1,198 @@ |
|||
name: Build |
|||
|
|||
on: |
|||
push: |
|||
branches: |
|||
- master |
|||
tags: |
|||
- "v*" |
|||
pull_request: |
|||
branches: |
|||
- master |
|||
push: |
|||
branches: |
|||
- main |
|||
tags: |
|||
- "v*" |
|||
pull_request: |
|||
branches: |
|||
- main |
|||
jobs: |
|||
Build: |
|||
strategy: |
|||
matrix: |
|||
options: |
|||
- os: ubuntu-latest |
|||
framework: net6.0 |
|||
sdk: 6.0.x |
|||
sdk-preview: true |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: macos-latest |
|||
framework: net6.0 |
|||
sdk: 6.0.x |
|||
sdk-preview: true |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: windows-latest |
|||
framework: net6.0 |
|||
sdk: 6.0.x |
|||
sdk-preview: true |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: ubuntu-latest |
|||
framework: net5.0 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: macos-latest |
|||
framework: net5.0 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: windows-latest |
|||
framework: net5.0 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: ubuntu-latest |
|||
framework: netcoreapp3.1 |
|||
runtime: -x64 |
|||
codecov: true |
|||
- os: macos-latest |
|||
framework: netcoreapp3.1 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: windows-latest |
|||
framework: netcoreapp3.1 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- 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: |
|||
- 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 Setup Preview |
|||
if: ${{ matrix.options.sdk-preview == true }} |
|||
uses: actions/setup-dotnet@v1 |
|||
with: |
|||
dotnet-version: ${{ matrix.options.sdk }} |
|||
include-prerelease: true |
|||
|
|||
- name: DotNet Build |
|||
if: ${{ matrix.options.sdk-preview != true }} |
|||
shell: pwsh |
|||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
|||
env: |
|||
SIXLABORS_TESTING: True |
|||
|
|||
- name: DotNet Build Preview |
|||
if: ${{ matrix.options.sdk-preview == true }} |
|||
shell: pwsh |
|||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
|||
env: |
|||
SIXLABORS_TESTING_PREVIEW: True |
|||
|
|||
- name: DotNet Test |
|||
if: ${{ matrix.options.sdk-preview != true }} |
|||
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: DotNet Test Preview |
|||
if: ${{ matrix.options.sdk-preview == true }} |
|||
shell: pwsh |
|||
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
|||
env: |
|||
SIXLABORS_TESTING_PREVIEW: 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 |
|||
|
|||
Publish: |
|||
needs: [Build] |
|||
|
|||
runs-on: ubuntu-latest |
|||
|
|||
if: (github.event_name == 'push') |
|||
|
|||
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 |
|||
|
|||
- 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 Pack |
|||
shell: pwsh |
|||
run: ./ci-pack.ps1 |
|||
|
|||
- 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 |
|||
Build: |
|||
strategy: |
|||
matrix: |
|||
options: |
|||
- os: ubuntu-latest |
|||
framework: net6.0 |
|||
sdk: 6.0.x |
|||
sdk-preview: true |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: macos-latest |
|||
framework: net6.0 |
|||
sdk: 6.0.x |
|||
sdk-preview: true |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: windows-latest |
|||
framework: net6.0 |
|||
sdk: 6.0.x |
|||
sdk-preview: true |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: ubuntu-latest |
|||
framework: net5.0 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: macos-latest |
|||
framework: net5.0 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: windows-latest |
|||
framework: net5.0 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: ubuntu-latest |
|||
framework: netcoreapp3.1 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: macos-latest |
|||
framework: netcoreapp3.1 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- os: windows-latest |
|||
framework: netcoreapp3.1 |
|||
runtime: -x64 |
|||
codecov: false |
|||
- 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: |
|||
- 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 Setup |
|||
uses: actions/setup-dotnet@v1 |
|||
with: |
|||
dotnet-version: | |
|||
6.0.x |
|||
5.0.x |
|||
3.1.x |
|||
2.1.x |
|||
|
|||
- name: DotNet Build |
|||
if: ${{ matrix.options.sdk-preview != true }} |
|||
shell: pwsh |
|||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
|||
env: |
|||
SIXLABORS_TESTING: True |
|||
|
|||
- name: DotNet Build Preview |
|||
if: ${{ matrix.options.sdk-preview == true }} |
|||
shell: pwsh |
|||
run: ./ci-build.ps1 "${{matrix.options.framework}}" |
|||
env: |
|||
SIXLABORS_TESTING_PREVIEW: True |
|||
|
|||
- name: DotNet Test |
|||
if: ${{ matrix.options.sdk-preview != true }} |
|||
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: DotNet Test Preview |
|||
if: ${{ matrix.options.sdk-preview == true }} |
|||
shell: pwsh |
|||
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" |
|||
env: |
|||
SIXLABORS_TESTING_PREVIEW: 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/ |
|||
|
|||
Publish: |
|||
needs: [Build] |
|||
|
|||
runs-on: ubuntu-latest |
|||
|
|||
if: (github.event_name == 'push') |
|||
|
|||
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 |
|||
|
|||
- 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 Pack |
|||
shell: pwsh |
|||
run: ./ci-pack.ps1 |
|||
|
|||
- 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 |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Threading; |
|||
|
|||
namespace SixLabors.ImageSharp.Diagnostics |
|||
{ |
|||
/// <summary>
|
|||
/// Represents the method to handle <see cref="MemoryDiagnostics.UndisposedAllocation"/>.
|
|||
/// </summary>
|
|||
public delegate void UndisposedAllocationDelegate(string allocationStackTrace); |
|||
|
|||
/// <summary>
|
|||
/// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects.
|
|||
/// </summary>
|
|||
public static class MemoryDiagnostics |
|||
{ |
|||
private static int totalUndisposedAllocationCount; |
|||
|
|||
private static UndisposedAllocationDelegate undisposedAllocation; |
|||
private static int undisposedAllocationSubscriptionCounter; |
|||
private static readonly object SyncRoot = new(); |
|||
|
|||
/// <summary>
|
|||
/// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer.
|
|||
/// The event brings significant overhead, and is intended to be used for troubleshooting only.
|
|||
/// For production diagnostics, use <see cref="TotalUndisposedAllocationCount"/>.
|
|||
/// </summary>
|
|||
public static event UndisposedAllocationDelegate UndisposedAllocation |
|||
{ |
|||
add |
|||
{ |
|||
lock (SyncRoot) |
|||
{ |
|||
undisposedAllocationSubscriptionCounter++; |
|||
undisposedAllocation += value; |
|||
} |
|||
} |
|||
|
|||
remove |
|||
{ |
|||
lock (SyncRoot) |
|||
{ |
|||
undisposedAllocation -= value; |
|||
undisposedAllocationSubscriptionCounter--; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
|
|||
/// </summary>
|
|||
public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount; |
|||
|
|||
internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; |
|||
|
|||
internal static void IncrementTotalUndisposedAllocationCount() => |
|||
Interlocked.Increment(ref totalUndisposedAllocationCount); |
|||
|
|||
internal static void DecrementTotalUndisposedAllocationCount() => |
|||
Interlocked.Decrement(ref totalUndisposedAllocationCount); |
|||
|
|||
internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) |
|||
{ |
|||
if (undisposedAllocation is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread.
|
|||
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER
|
|||
ThreadPool.QueueUserWorkItem( |
|||
stackTrace => undisposedAllocation?.Invoke(stackTrace), |
|||
allocationStackTrace, |
|||
preferLocal: false); |
|||
#else
|
|||
ThreadPool.QueueUserWorkItem( |
|||
stackTrace => undisposedAllocation?.Invoke((string)stackTrace), |
|||
allocationStackTrace); |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -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,201 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
|||
{ |
|||
/// <summary>
|
|||
/// Bitwriter for writing compressed CCITT T6 2D data.
|
|||
/// </summary>
|
|||
internal sealed class T6BitCompressor : TiffCcittCompressor |
|||
{ |
|||
/// <summary>
|
|||
/// Vertical codes from -3 to +3.
|
|||
/// </summary>
|
|||
private static readonly (uint Length, uint Code)[] VerticalCodes = |
|||
{ |
|||
(7u, 3u), |
|||
(6u, 3u), |
|||
(3u, 3u), |
|||
(1u, 1u), |
|||
(3u, 2u), |
|||
(6u, 2u), |
|||
(7u, 2u) |
|||
}; |
|||
|
|||
private IMemoryOwner<byte> referenceLineBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="T6BitCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The output stream to write the compressed data.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="width">The width of the image.</param>
|
|||
/// <param name="bitsPerPixel">The bits per pixel.</param>
|
|||
public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) |
|||
: base(output, allocator, width, bitsPerPixel) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override TiffCompression Method => TiffCompression.CcittGroup4Fax; |
|||
|
|||
/// <summary>
|
|||
/// Writes a image compressed with CCITT T6 to the output buffer.
|
|||
/// </summary>
|
|||
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
|
|||
/// <param name="height">The strip height.</param>
|
|||
/// <param name="compressedData">The destination for the compressed data.</param>
|
|||
protected override void CompressStrip(Span<byte> pixelsAsGray, int height, Span<byte> compressedData) |
|||
{ |
|||
// Initial reference line is all white.
|
|||
Span<byte> referenceLine = this.referenceLineBuffer.GetSpan(); |
|||
referenceLine.Fill(0xff); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<byte> row = pixelsAsGray.Slice(y * this.Width, this.Width); |
|||
uint a0 = 0; |
|||
uint a1 = row[0] == 0 ? 0 : this.FindRunEnd(row, 0); |
|||
uint b1 = referenceLine[0] == 0 ? 0 : this.FindRunEnd(referenceLine, 0); |
|||
|
|||
while (true) |
|||
{ |
|||
uint b2 = this.FindRunEnd(referenceLine, b1); |
|||
if (b2 < a1) |
|||
{ |
|||
// Pass mode.
|
|||
this.WriteCode(4, 1, compressedData); |
|||
a0 = b2; |
|||
} |
|||
else |
|||
{ |
|||
int d = int.MaxValue; |
|||
if ((b1 >= a1) && (b1 - a1 <= 3)) |
|||
{ |
|||
d = (int)(b1 - a1); |
|||
} |
|||
else if ((b1 < a1) && (a1 - b1 <= 3)) |
|||
{ |
|||
d = -(int)(a1 - b1); |
|||
} |
|||
|
|||
if ((d >= -3) && (d <= 3)) |
|||
{ |
|||
// Vertical mode.
|
|||
(uint length, uint code) = VerticalCodes[d + 3]; |
|||
this.WriteCode(length, code, compressedData); |
|||
a0 = a1; |
|||
} |
|||
else |
|||
{ |
|||
// Horizontal mode.
|
|||
this.WriteCode(3, 1, compressedData); |
|||
|
|||
uint a2 = this.FindRunEnd(row, a1); |
|||
if ((a0 + a1 == 0) || (row[(int)a0] != 0)) |
|||
{ |
|||
this.WriteRun(a1 - a0, true, compressedData); |
|||
this.WriteRun(a2 - a1, false, compressedData); |
|||
} |
|||
else |
|||
{ |
|||
this.WriteRun(a1 - a0, false, compressedData); |
|||
this.WriteRun(a2 - a1, true, compressedData); |
|||
} |
|||
|
|||
a0 = a2; |
|||
} |
|||
} |
|||
|
|||
if (a0 >= row.Length) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
byte thisPixel = row[(int)a0]; |
|||
a1 = this.FindRunEnd(row, a0, thisPixel); |
|||
b1 = this.FindRunEnd(referenceLine, a0, (byte)~thisPixel); |
|||
b1 = this.FindRunEnd(referenceLine, b1, thisPixel); |
|||
} |
|||
|
|||
// This row is now the reference line.
|
|||
row.CopyTo(referenceLine); |
|||
} |
|||
|
|||
this.WriteCode(12, 1, compressedData); |
|||
this.WriteCode(12, 1, compressedData); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
this.referenceLineBuffer?.Dispose(); |
|||
base.Dispose(disposing); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds the end of a pixel run.
|
|||
/// </summary>
|
|||
/// <param name="row">The row of pixels to examine.</param>
|
|||
/// <param name="startIndex">The index of the first pixel in <paramref name="row"/> to examine.</param>
|
|||
/// <param name="color">Color of pixels in the run. If not specified, the color at
|
|||
/// <paramref name="startIndex"/> will be used.</param>
|
|||
/// <returns>The index of the first pixel at or after <paramref name="startIndex"/>
|
|||
/// that does not match <paramref name="color"/>, or the length of <paramref name="row"/>,
|
|||
/// whichever comes first.</returns>
|
|||
private uint FindRunEnd(Span<byte> row, uint startIndex, byte? color = null) |
|||
{ |
|||
if (startIndex >= row.Length) |
|||
{ |
|||
return (uint)row.Length; |
|||
} |
|||
|
|||
byte colorValue = color.GetValueOrDefault(row[(int)startIndex]); |
|||
for (int i = (int)startIndex; i < row.Length; i++) |
|||
{ |
|||
if (row[i] != colorValue) |
|||
{ |
|||
return (uint)i; |
|||
} |
|||
} |
|||
|
|||
return (uint)row.Length; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Initialize(int rowsPerStrip) |
|||
{ |
|||
base.Initialize(rowsPerStrip); |
|||
this.referenceLineBuffer = this.Allocator.Allocate<byte>(this.Width); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a run to the output buffer.
|
|||
/// </summary>
|
|||
/// <param name="runLength">The length of the run.</param>
|
|||
/// <param name="isWhiteRun">If <c>true</c> the run is white pixels,
|
|||
/// if <c>false</c> the run is black pixels.</param>
|
|||
/// <param name="compressedData">The destination to write the run to.</param>
|
|||
private void WriteRun(uint runLength, bool isWhiteRun, Span<byte> compressedData) |
|||
{ |
|||
uint code; |
|||
uint codeLength; |
|||
while (runLength > 63) |
|||
{ |
|||
uint makeupLength = this.GetBestFittingMakeupRunLength(runLength); |
|||
code = this.GetMakeupCode(makeupLength, out codeLength, isWhiteRun); |
|||
this.WriteCode(codeLength, code, compressedData); |
|||
runLength -= makeupLength; |
|||
} |
|||
|
|||
code = this.GetTermCode(runLength, out codeLength, isWhiteRun); |
|||
this.WriteCode(codeLength, code, compressedData); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,536 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
|||
{ |
|||
/// <summary>
|
|||
/// Common functionality for CCITT T4 and T6 Compression
|
|||
/// </summary>
|
|||
internal abstract class TiffCcittCompressor : TiffBaseCompressor |
|||
{ |
|||
protected const uint WhiteZeroRunTermCode = 0x35; |
|||
|
|||
protected const uint BlackZeroRunTermCode = 0x37; |
|||
|
|||
private static readonly uint[] MakeupRunLength = |
|||
{ |
|||
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, |
|||
{ 27, 0x24 }, { 28, 0x18 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, |
|||
{ 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, |
|||
{ 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, |
|||
{ 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, |
|||
{ 63, 0x34 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 2, 0x3 }, { 3, 0x2 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 1, 0x2 }, { 4, 0x3 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 5, 0x3 }, { 6, 0x2 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 7, 0x3 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 8, 0x5 }, { 9, 0x4 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 13, 0x4 }, { 14, 0x7 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 15, 0x18 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, |
|||
{ 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, |
|||
{ 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, |
|||
{ 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, |
|||
{ 62, 0x66 }, { 63, 0x67 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 64, 0x1B }, { 128, 0x12 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 192, 0x17 }, { 1664, 0x18 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 256, 0x37 } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, |
|||
{ 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, |
|||
{ 1600, 0x9A }, { 1728, 0x9B } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, |
|||
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 64, 0xF } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, |
|||
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, |
|||
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } |
|||
}; |
|||
|
|||
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>() |
|||
{ |
|||
{ 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, |
|||
{ 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, |
|||
{ 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } |
|||
}; |
|||
|
|||
private int bytePosition; |
|||
|
|||
private byte bitPosition; |
|||
|
|||
private IMemoryOwner<byte> compressedDataBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffCcittCompressor" /> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The output.</param>
|
|||
/// <param name="allocator">The allocator.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="bitsPerPixel">The bits per pixel.</param>
|
|||
protected TiffCcittCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) |
|||
: base(output, allocator, width, bitsPerPixel) |
|||
{ |
|||
DebugGuard.IsTrue(bitsPerPixel == 1, nameof(bitsPerPixel), "CCITT compression requires one bit per pixel"); |
|||
this.bytePosition = 0; |
|||
this.bitPosition = 0; |
|||
} |
|||
|
|||
private uint GetWhiteMakeupCode(uint runLength, out uint codeLength) |
|||
{ |
|||
codeLength = 0; |
|||
|
|||
if (WhiteLen5MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 5; |
|||
return WhiteLen5MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen6MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 6; |
|||
return WhiteLen6MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen7MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 7; |
|||
return WhiteLen7MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen8MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 8; |
|||
return WhiteLen8MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen9MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 9; |
|||
return WhiteLen9MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen11MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 11; |
|||
return WhiteLen11MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen12MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 12; |
|||
return WhiteLen12MakeupCodes[runLength]; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
private uint GetBlackMakeupCode(uint runLength, out uint codeLength) |
|||
{ |
|||
codeLength = 0; |
|||
|
|||
if (BlackLen10MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 10; |
|||
return BlackLen10MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen11MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 11; |
|||
return BlackLen11MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen12MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 12; |
|||
return BlackLen12MakeupCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen13MakeupCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 13; |
|||
return BlackLen13MakeupCodes[runLength]; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
private uint GetWhiteTermCode(uint runLength, out uint codeLength) |
|||
{ |
|||
codeLength = 0; |
|||
|
|||
if (WhiteLen4TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 4; |
|||
return WhiteLen4TermCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen5TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 5; |
|||
return WhiteLen5TermCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen6TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 6; |
|||
return WhiteLen6TermCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen7TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 7; |
|||
return WhiteLen7TermCodes[runLength]; |
|||
} |
|||
|
|||
if (WhiteLen8TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 8; |
|||
return WhiteLen8TermCodes[runLength]; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
private uint GetBlackTermCode(uint runLength, out uint codeLength) |
|||
{ |
|||
codeLength = 0; |
|||
|
|||
if (BlackLen2TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 2; |
|||
return BlackLen2TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen3TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 3; |
|||
return BlackLen3TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen4TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 4; |
|||
return BlackLen4TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen5TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 5; |
|||
return BlackLen5TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen6TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 6; |
|||
return BlackLen6TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen7TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 7; |
|||
return BlackLen7TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen8TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 8; |
|||
return BlackLen8TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen9TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 9; |
|||
return BlackLen9TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen10TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 10; |
|||
return BlackLen10TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen11TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 11; |
|||
return BlackLen11TermCodes[runLength]; |
|||
} |
|||
|
|||
if (BlackLen12TermCodes.ContainsKey(runLength)) |
|||
{ |
|||
codeLength = 12; |
|||
return BlackLen12TermCodes[runLength]; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the best makeup run length for a given run length
|
|||
/// </summary>
|
|||
/// <param name="runLength">A run length needing a makeup code</param>
|
|||
/// <returns>The makeup length for <paramref name="runLength"/>.</returns>
|
|||
protected uint GetBestFittingMakeupRunLength(uint runLength) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(runLength, MakeupRunLength[0], nameof(runLength)); |
|||
|
|||
for (int i = 0; i < MakeupRunLength.Length - 1; i++) |
|||
{ |
|||
if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) |
|||
{ |
|||
return MakeupRunLength[i]; |
|||
} |
|||
} |
|||
|
|||
return MakeupRunLength[MakeupRunLength.Length - 1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the terminating code for a run length.
|
|||
/// </summary>
|
|||
/// <param name="runLength">The run length to get the terminating code for.</param>
|
|||
/// <param name="codeLength">The length of the terminating code.</param>
|
|||
/// <param name="isWhiteRun">If <c>true</c>, the run is of white pixels.
|
|||
/// If <c>false</c> the run is of black pixels</param>
|
|||
/// <returns>The terminating code for a run of length <paramref name="runLength"/></returns>
|
|||
protected uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) |
|||
{ |
|||
if (isWhiteRun) |
|||
{ |
|||
return this.GetWhiteTermCode(runLength, out codeLength); |
|||
} |
|||
|
|||
return this.GetBlackTermCode(runLength, out codeLength); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the makeup code for a run length.
|
|||
/// </summary>
|
|||
/// <param name="runLength">The run length to get the makeup code for.</param>
|
|||
/// <param name="codeLength">The length of the makeup code.</param>
|
|||
/// <param name="isWhiteRun">If <c>true</c>, the run is of white pixels.
|
|||
/// If <c>false</c> the run is of black pixels</param>
|
|||
/// <returns>The makeup code for a run of length <paramref name="runLength"/></returns>
|
|||
protected uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) |
|||
{ |
|||
if (isWhiteRun) |
|||
{ |
|||
return this.GetWhiteMakeupCode(runLength, out codeLength); |
|||
} |
|||
|
|||
return this.GetBlackMakeupCode(runLength, out codeLength); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Pads output to the next byte
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If the output is not currently on a byte boundary,
|
|||
/// zero-pad it to the next byte
|
|||
/// </remarks>
|
|||
protected void PadByte() |
|||
{ |
|||
// Check if padding is necessary.
|
|||
if (this.bitPosition % 8 != 0) |
|||
{ |
|||
// Skip padding bits, move to next byte.
|
|||
this.bytePosition++; |
|||
this.bitPosition = 0; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a code to the output.
|
|||
/// </summary>
|
|||
/// <param name="codeLength">The length of the code to write.</param>
|
|||
/// <param name="code">The code to be written.</param>
|
|||
/// <param name="compressedData">The destination buffer to write the code to.</param>
|
|||
protected void WriteCode(uint codeLength, uint code, Span<byte> compressedData) |
|||
{ |
|||
while (codeLength > 0) |
|||
{ |
|||
int bitNumber = (int)codeLength; |
|||
bool bit = (code & (1 << (bitNumber - 1))) != 0; |
|||
if (bit) |
|||
{ |
|||
BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); |
|||
} |
|||
else |
|||
{ |
|||
BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); |
|||
} |
|||
|
|||
this.bitPosition++; |
|||
if (this.bitPosition == 8) |
|||
{ |
|||
this.bytePosition++; |
|||
this.bitPosition = 0; |
|||
} |
|||
|
|||
codeLength--; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a image compressed with CCITT T6 to the stream.
|
|||
/// </summary>
|
|||
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
|
|||
/// <param name="height">The strip height.</param>
|
|||
public override void CompressStrip(Span<byte> pixelsAsGray, int height) |
|||
{ |
|||
DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals"); |
|||
DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals"); |
|||
|
|||
this.compressedDataBuffer.Clear(); |
|||
Span<byte> compressedData = this.compressedDataBuffer.GetSpan(); |
|||
|
|||
this.bytePosition = 0; |
|||
this.bitPosition = 0; |
|||
|
|||
this.CompressStrip(pixelsAsGray, height, compressedData); |
|||
|
|||
// Write the compressed data to the stream.
|
|||
int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; |
|||
this.Output.Write(compressedData.Slice(0, bytesToWrite)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compress a data strip
|
|||
/// </summary>
|
|||
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
|
|||
/// <param name="height">The strip height.</param>
|
|||
/// <param name="compressedData">The destination for the compressed data.</param>
|
|||
protected abstract void CompressStrip(Span<byte> pixelsAsGray, int height, Span<byte> compressedData); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Initialize(int rowsPerStrip) |
|||
{ |
|||
// This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good.
|
|||
int maxNeededBytes = this.Width * rowsPerStrip; |
|||
this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes); |
|||
} |
|||
} |
|||
} |
|||
@ -1,64 +1,78 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal class EntryReader : BaseExifReader |
|||
{ |
|||
private readonly uint startOffset; |
|||
|
|||
private readonly SortedList<uint, Action> lazyLoaders; |
|||
|
|||
public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList<uint, Action> lazyLoaders) |
|||
: base(stream) |
|||
{ |
|||
public EntryReader(Stream stream, ByteOrder byteOrder, MemoryAllocator allocator) |
|||
: base(stream, allocator) => |
|||
this.IsBigEndian = byteOrder == ByteOrder.BigEndian; |
|||
this.startOffset = ifdOffset; |
|||
this.lazyLoaders = lazyLoaders; |
|||
} |
|||
|
|||
public List<IExifValue> Values { get; } = new(); |
|||
|
|||
public uint NextIfdOffset { get; private set; } |
|||
public ulong NextIfdOffset { get; private set; } |
|||
|
|||
public void ReadTags() |
|||
public void ReadTags(bool isBigTiff, ulong ifdOffset) |
|||
{ |
|||
this.ReadValues(this.Values, this.startOffset); |
|||
this.NextIfdOffset = this.ReadUInt32(); |
|||
if (!isBigTiff) |
|||
{ |
|||
this.ReadValues(this.Values, (uint)ifdOffset); |
|||
this.NextIfdOffset = this.ReadUInt32(); |
|||
|
|||
this.ReadSubIfd(this.Values); |
|||
} |
|||
else |
|||
{ |
|||
this.ReadValues64(this.Values, ifdOffset); |
|||
this.NextIfdOffset = this.ReadUInt64(); |
|||
|
|||
this.ReadSubIfd(this.Values); |
|||
//// this.ReadSubIfd64(this.Values);
|
|||
} |
|||
} |
|||
|
|||
protected override void RegisterExtLoader(uint offset, Action reader) => |
|||
this.lazyLoaders.Add(offset, reader); |
|||
public void ReadBigValues() => this.ReadBigValues(this.Values); |
|||
} |
|||
|
|||
internal class HeaderReader : BaseExifReader |
|||
{ |
|||
public HeaderReader(Stream stream, ByteOrder byteOrder) |
|||
: base(stream) => |
|||
: base(stream, null) => |
|||
this.IsBigEndian = byteOrder == ByteOrder.BigEndian; |
|||
|
|||
public uint FirstIfdOffset { get; private set; } |
|||
public bool IsBigTiff { get; private set; } |
|||
|
|||
public uint ReadFileHeader() |
|||
public ulong FirstIfdOffset { get; private set; } |
|||
|
|||
public void ReadFileHeader() |
|||
{ |
|||
ushort magic = this.ReadUInt16(); |
|||
if (magic != TiffConstants.HeaderMagicNumber) |
|||
if (magic == TiffConstants.HeaderMagicNumber) |
|||
{ |
|||
TiffThrowHelper.ThrowInvalidHeader(); |
|||
this.IsBigTiff = false; |
|||
this.FirstIfdOffset = this.ReadUInt32(); |
|||
return; |
|||
} |
|||
else if (magic == TiffConstants.BigTiffHeaderMagicNumber) |
|||
{ |
|||
this.IsBigTiff = true; |
|||
|
|||
this.FirstIfdOffset = this.ReadUInt32(); |
|||
return this.FirstIfdOffset; |
|||
} |
|||
ushort bytesize = this.ReadUInt16(); |
|||
ushort reserve = this.ReadUInt16(); |
|||
if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0) |
|||
{ |
|||
this.FirstIfdOffset = this.ReadUInt64(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException(); |
|||
TiffThrowHelper.ThrowInvalidHeader(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The TIFF format type enum.
|
|||
/// </summary>
|
|||
public enum TiffFormatType |
|||
{ |
|||
/// <summary>
|
|||
/// The TIFF file format type.
|
|||
/// </summary>
|
|||
Default, |
|||
|
|||
/// <summary>
|
|||
/// The BigTIFF format type.
|
|||
/// </summary>
|
|||
BigTIFF |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Formats.Webp.Lossless; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Webp |
|||
{ |
|||
/// <summary>
|
|||
/// Methods for encoding the alpha data of a VP8 image.
|
|||
/// </summary>
|
|||
internal class AlphaEncoder : IDisposable |
|||
{ |
|||
private IMemoryOwner<byte> alphaData; |
|||
|
|||
/// <summary>
|
|||
/// Encodes the alpha channel data.
|
|||
/// Data is either compressed as lossless webp image or uncompressed.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="configuration">The global configuration.</param>
|
|||
/// <param name="memoryAllocator">The memory manager.</param>
|
|||
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
|
|||
/// <param name="size">The size in bytes of the alpha data.</param>
|
|||
/// <returns>The encoded alpha data.</returns>
|
|||
public IMemoryOwner<byte> EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); |
|||
|
|||
if (compress) |
|||
{ |
|||
WebpEncodingMethod effort = WebpEncodingMethod.Default; |
|||
int quality = 8 * (int)effort; |
|||
using var lossLessEncoder = new Vp8LEncoder( |
|||
memoryAllocator, |
|||
configuration, |
|||
width, |
|||
height, |
|||
quality, |
|||
effort, |
|||
WebpTransparentColorMode.Preserve, |
|||
false, |
|||
0); |
|||
|
|||
// The transparency information will be stored in the green channel of the ARGB quadruplet.
|
|||
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
|
|||
// that can improve compression.
|
|||
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan()); |
|||
|
|||
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData); |
|||
|
|||
return this.alphaData; |
|||
} |
|||
|
|||
size = width * height; |
|||
return this.alphaData; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Store the transparency in the green channel.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="alphaData">A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</param>
|
|||
/// <returns>The transparency image.</returns>
|
|||
private static Image<Rgba32> DispatchAlphaToGreen<TPixel>(Image<TPixel> image, Span<byte> alphaData) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
var alphaAsImage = new Image<Rgba32>(width, height); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Memory<Rgba32> rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y); |
|||
Span<Rgba32> pixelRow = rowBuffer.Span; |
|||
Span<byte> alphaRow = alphaData.Slice(y * width, width); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// Leave A/R/B channels zero'd.
|
|||
pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0); |
|||
} |
|||
} |
|||
|
|||
return alphaAsImage; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extract the alpha data of the image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="configuration">The global configuration.</param>
|
|||
/// <param name="memoryAllocator">The memory manager.</param>
|
|||
/// <returns>A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</returns>
|
|||
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer; |
|||
int height = image.Height; |
|||
int width = image.Width; |
|||
IMemoryOwner<byte> alphaDataBuffer = memoryAllocator.Allocate<byte>(width * height); |
|||
Span<byte> alphaData = alphaDataBuffer.GetSpan(); |
|||
|
|||
using IMemoryOwner<Rgba32> rowBuffer = memoryAllocator.Allocate<Rgba32>(width); |
|||
Span<Rgba32> rgbaRow = rowBuffer.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToRgba32(configuration, rowSpan, rgbaRow); |
|||
int offset = y * width; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
alphaData[offset + x] = rgbaRow[x].A; |
|||
} |
|||
} |
|||
|
|||
return alphaDataBuffer; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() => this.alphaData?.Dispose(); |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Implements reference counting lifetime guard mechanism similar to the one provided by <see cref="SafeHandle"/>,
|
|||
/// but without the restriction of the guarded object being a handle.
|
|||
/// </summary>
|
|||
internal abstract class RefCountedLifetimeGuard : IDisposable |
|||
{ |
|||
private int refCount = 1; |
|||
private int disposed; |
|||
private int released; |
|||
|
|||
~RefCountedLifetimeGuard() |
|||
{ |
|||
Interlocked.Exchange(ref this.disposed, 1); |
|||
this.ReleaseRef(); |
|||
} |
|||
|
|||
public bool IsDisposed => this.disposed == 1; |
|||
|
|||
public void AddRef() => Interlocked.Increment(ref this.refCount); |
|||
|
|||
public void ReleaseRef() |
|||
{ |
|||
Interlocked.Decrement(ref this.refCount); |
|||
if (this.refCount == 0) |
|||
{ |
|||
int wasReleased = Interlocked.Exchange(ref this.released, 1); |
|||
|
|||
if (wasReleased == 0) |
|||
{ |
|||
this.Release(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); |
|||
if (wasDisposed == 0) |
|||
{ |
|||
this.ReleaseRef(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
|
|||
protected abstract void Release(); |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Implements reference counting lifetime guard mechanism for memory resources
|
|||
/// and maintains the value of <see cref="MemoryDiagnostics.TotalUndisposedAllocationCount"/>.
|
|||
/// </summary>
|
|||
internal abstract class RefCountedMemoryLifetimeGuard : IDisposable |
|||
{ |
|||
private int refCount = 1; |
|||
private int disposed; |
|||
private int released; |
|||
private string allocationStackTrace; |
|||
|
|||
protected RefCountedMemoryLifetimeGuard() |
|||
{ |
|||
if (MemoryDiagnostics.UndisposedAllocationSubscribed) |
|||
{ |
|||
this.allocationStackTrace = Environment.StackTrace; |
|||
} |
|||
|
|||
MemoryDiagnostics.IncrementTotalUndisposedAllocationCount(); |
|||
} |
|||
|
|||
~RefCountedMemoryLifetimeGuard() |
|||
{ |
|||
Interlocked.Exchange(ref this.disposed, 1); |
|||
this.ReleaseRef(true); |
|||
} |
|||
|
|||
public bool IsDisposed => this.disposed == 1; |
|||
|
|||
public void AddRef() => Interlocked.Increment(ref this.refCount); |
|||
|
|||
public void ReleaseRef() => this.ReleaseRef(false); |
|||
|
|||
public void Dispose() |
|||
{ |
|||
int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); |
|||
if (wasDisposed == 0) |
|||
{ |
|||
this.ReleaseRef(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
|
|||
protected abstract void Release(); |
|||
|
|||
private void ReleaseRef(bool finalizing) |
|||
{ |
|||
Interlocked.Decrement(ref this.refCount); |
|||
if (this.refCount == 0) |
|||
{ |
|||
int wasReleased = Interlocked.Exchange(ref this.released, 1); |
|||
|
|||
if (wasReleased == 0) |
|||
{ |
|||
if (!finalizing) |
|||
{ |
|||
MemoryDiagnostics.DecrementTotalUndisposedAllocationCount(); |
|||
} |
|||
else if (this.allocationStackTrace != null) |
|||
{ |
|||
MemoryDiagnostics.RaiseUndisposedMemoryResource(this.allocationStackTrace); |
|||
} |
|||
|
|||
this.Release(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,121 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.Text; |
|||
using static SixLabors.ImageSharp.Metadata.Profiles.Exif.EncodedString; |
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
internal static class ExifEncodedStringHelpers |
|||
{ |
|||
public const int CharacterCodeBytesLength = 8; |
|||
|
|||
private const ulong AsciiCode = 0x_00_00_00_49_49_43_53_41; |
|||
private const ulong JISCode = 0x_00_00_00_00_00_53_49_4A; |
|||
private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55; |
|||
private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; |
|||
|
|||
private static ReadOnlySpan<byte> AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 }; |
|||
|
|||
private static ReadOnlySpan<byte> JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 }; |
|||
|
|||
private static ReadOnlySpan<byte> UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 }; |
|||
|
|||
private static ReadOnlySpan<byte> UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; |
|||
|
|||
// 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990)
|
|||
// https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0
|
|||
private static Encoding JIS0208Encoding => CodePagesEncodingProvider.Instance.GetEncoding(20932); |
|||
|
|||
public static bool IsEncodedString(ExifTagValue tag) => tag switch |
|||
{ |
|||
ExifTagValue.UserComment or ExifTagValue.GPSProcessingMethod or ExifTagValue.GPSAreaInformation => true, |
|||
_ => false |
|||
}; |
|||
|
|||
public static ReadOnlySpan<byte> GetCodeBytes(CharacterCode code) => code switch |
|||
{ |
|||
CharacterCode.ASCII => AsciiCodeBytes, |
|||
CharacterCode.JIS => JISCodeBytes, |
|||
CharacterCode.Unicode => UnicodeCodeBytes, |
|||
CharacterCode.Undefined => UndefinedCodeBytes, |
|||
_ => UndefinedCodeBytes |
|||
}; |
|||
|
|||
public static Encoding GetEncoding(CharacterCode code) => code switch |
|||
{ |
|||
CharacterCode.ASCII => Encoding.ASCII, |
|||
CharacterCode.JIS => JIS0208Encoding, |
|||
CharacterCode.Unicode => Encoding.Unicode, |
|||
CharacterCode.Undefined => Encoding.UTF8, |
|||
_ => Encoding.UTF8 |
|||
}; |
|||
|
|||
public static bool TryParse(ReadOnlySpan<byte> buffer, out EncodedString encodedString) |
|||
{ |
|||
if (TryDetect(buffer, out CharacterCode code)) |
|||
{ |
|||
string text = GetEncoding(code).GetString(buffer.Slice(CharacterCodeBytesLength)); |
|||
encodedString = new EncodedString(code, text); |
|||
return true; |
|||
} |
|||
|
|||
encodedString = default; |
|||
return false; |
|||
} |
|||
|
|||
public static uint GetDataLength(EncodedString encodedString) => |
|||
(uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; |
|||
|
|||
public static int Write(EncodedString encodedString, Span<byte> destination) |
|||
{ |
|||
GetCodeBytes(encodedString.Code).CopyTo(destination); |
|||
|
|||
string text = encodedString.Text; |
|||
int count = Write(GetEncoding(encodedString.Code), text, destination.Slice(CharacterCodeBytesLength)); |
|||
|
|||
return CharacterCodeBytesLength + count; |
|||
} |
|||
|
|||
public static unsafe int Write(Encoding encoding, string value, Span<byte> destination) |
|||
{ |
|||
fixed (char* c = value) |
|||
{ |
|||
fixed (byte* b = destination) |
|||
{ |
|||
return encoding.GetBytes(c, value.Length, b, destination.Length); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code) |
|||
{ |
|||
if (buffer.Length >= CharacterCodeBytesLength) |
|||
{ |
|||
ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer); |
|||
switch (test) |
|||
{ |
|||
case AsciiCode: |
|||
code = CharacterCode.ASCII; |
|||
return true; |
|||
case JISCode: |
|||
code = CharacterCode.JIS; |
|||
return true; |
|||
case UnicodeCode: |
|||
code = CharacterCode.Unicode; |
|||
return true; |
|||
case UndefinedCode: |
|||
code = CharacterCode.Undefined; |
|||
return true; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
code = default; |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
internal static class ExifUcs2StringHelpers |
|||
{ |
|||
public static Encoding Ucs2Encoding => Encoding.GetEncoding("UCS-2"); |
|||
|
|||
public static bool IsUcs2Tag(ExifTagValue tag) => tag switch |
|||
{ |
|||
ExifTagValue.XPAuthor or ExifTagValue.XPComment or ExifTagValue.XPKeywords or ExifTagValue.XPSubject or ExifTagValue.XPTitle => true, |
|||
_ => false, |
|||
}; |
|||
|
|||
public static int Write(string value, Span<byte> destination) => ExifEncodedStringHelpers.Write(Ucs2Encoding, value, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
/// <content/>
|
|||
public abstract partial class ExifTag |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the UserComment exif tag.
|
|||
/// </summary>
|
|||
public static ExifTag<EncodedString> UserComment { get; } = new ExifTag<EncodedString>(ExifTagValue.UserComment); |
|||
|
|||
/// <summary>
|
|||
/// Gets the GPSProcessingMethod exif tag.
|
|||
/// </summary>
|
|||
public static ExifTag<EncodedString> GPSProcessingMethod { get; } = new ExifTag<EncodedString>(ExifTagValue.GPSProcessingMethod); |
|||
|
|||
/// <summary>
|
|||
/// Gets the GPSAreaInformation exif tag.
|
|||
/// </summary>
|
|||
public static ExifTag<EncodedString> GPSAreaInformation { get; } = new ExifTag<EncodedString>(ExifTagValue.GPSAreaInformation); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
/// <content/>
|
|||
public abstract partial class ExifTag |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the title tag used by Windows (encoded in UCS2).
|
|||
/// </summary>
|
|||
public static ExifTag<string> XPTitle => new ExifTag<string>(ExifTagValue.XPTitle); |
|||
|
|||
/// <summary>
|
|||
/// Gets the comment tag used by Windows (encoded in UCS2).
|
|||
/// </summary>
|
|||
public static ExifTag<string> XPComment => new ExifTag<string>(ExifTagValue.XPComment); |
|||
|
|||
/// <summary>
|
|||
/// Gets the author tag used by Windows (encoded in UCS2).
|
|||
/// </summary>
|
|||
public static ExifTag<string> XPAuthor => new ExifTag<string>(ExifTagValue.XPAuthor); |
|||
|
|||
/// <summary>
|
|||
/// Gets the keywords tag used by Windows (encoded in UCS2).
|
|||
/// </summary>
|
|||
public static ExifTag<string> XPKeywords => new ExifTag<string>(ExifTagValue.XPKeywords); |
|||
|
|||
/// <summary>
|
|||
/// Gets the subject tag used by Windows (encoded in UCS2).
|
|||
/// </summary>
|
|||
public static ExifTag<string> XPSubject => new ExifTag<string>(ExifTagValue.XPSubject); |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
/// <summary>
|
|||
/// The EXIF encoded string structure.
|
|||
/// </summary>
|
|||
public readonly struct EncodedString : IEquatable<EncodedString> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EncodedString" /> struct.
|
|||
/// Default use Unicode character code.
|
|||
/// </summary>
|
|||
/// <param name="text">The text value.</param>
|
|||
public EncodedString(string text) |
|||
: this(CharacterCode.Unicode, text) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EncodedString" /> struct.
|
|||
/// </summary>
|
|||
/// <param name="code">The character code.</param>
|
|||
/// <param name="text">The text value.</param>
|
|||
public EncodedString(CharacterCode code, string text) |
|||
{ |
|||
this.Text = text; |
|||
this.Code = code; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The 8-byte character code enum.
|
|||
/// </summary>
|
|||
public enum CharacterCode |
|||
{ |
|||
/// <summary>
|
|||
/// The ASCII (ITU-T T.50 IA5) character code.
|
|||
/// </summary>
|
|||
ASCII, |
|||
|
|||
/// <summary>
|
|||
/// The JIS (X208-1990) character code.
|
|||
/// </summary>
|
|||
JIS, |
|||
|
|||
/// <summary>
|
|||
/// The Unicode character code.
|
|||
/// </summary>
|
|||
Unicode, |
|||
|
|||
/// <summary>
|
|||
/// The undefined character code.
|
|||
/// </summary>
|
|||
Undefined |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the character ode.
|
|||
/// </summary>
|
|||
public CharacterCode Code { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the text.
|
|||
/// </summary>
|
|||
public string Text { get; } |
|||
|
|||
/// <summary>
|
|||
/// Converts the specified <see cref="string"/> to an instance of this type.
|
|||
/// </summary>
|
|||
/// <param name="text">The text value.</param>
|
|||
public static implicit operator EncodedString(string text) => new(text); |
|||
|
|||
/// <summary>
|
|||
/// Converts the specified <see cref="EncodedString"/> to a <see cref="string"/>.
|
|||
/// </summary>
|
|||
/// <param name="encodedString">The <see cref="EncodedString"/> to convert.</param>
|
|||
public static explicit operator string(EncodedString encodedString) => encodedString.Text; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(EncodedString other) => this.Text == other.Text && this.Code == other.Code; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.Text, this.Code); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => this.Text; |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Globalization; |
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
internal sealed class ExifEncodedString : ExifValue<EncodedString> |
|||
{ |
|||
public ExifEncodedString(ExifTag<EncodedString> tag) |
|||
: base(tag) |
|||
{ |
|||
} |
|||
|
|||
public ExifEncodedString(ExifTagValue tag) |
|||
: base(tag) |
|||
{ |
|||
} |
|||
|
|||
private ExifEncodedString(ExifEncodedString value) |
|||
: base(value) |
|||
{ |
|||
} |
|||
|
|||
public override ExifDataType DataType => ExifDataType.Undefined; |
|||
|
|||
protected override string StringValue => this.Value.Text; |
|||
|
|||
public override bool TrySetValue(object value) |
|||
{ |
|||
if (base.TrySetValue(value)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (value is string stringValue) |
|||
{ |
|||
this.Value = new EncodedString(stringValue); |
|||
return true; |
|||
} |
|||
else if (value is byte[] buffer) |
|||
{ |
|||
if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString)) |
|||
{ |
|||
this.Value = encodedString; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public override IExifValue DeepClone() => new ExifEncodedString(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Globalization; |
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
internal sealed class ExifLong8 : ExifValue<ulong> |
|||
{ |
|||
public ExifLong8(ExifTag<ulong> tag) |
|||
: base(tag) |
|||
{ |
|||
} |
|||
|
|||
public ExifLong8(ExifTagValue tag) |
|||
: base(tag) |
|||
{ |
|||
} |
|||
|
|||
private ExifLong8(ExifLong8 value) |
|||
: base(value) |
|||
{ |
|||
} |
|||
|
|||
public override ExifDataType DataType => ExifDataType.Long8; |
|||
|
|||
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 long intValue: |
|||
if (intValue >= 0) |
|||
{ |
|||
this.Value = (ulong)intValue; |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
default: |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public override IExifValue DeepClone() => new ExifLong8(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif |
|||
{ |
|||
internal sealed class ExifLong8Array : ExifArrayValue<ulong> |
|||
{ |
|||
public ExifLong8Array(ExifTagValue tag) |
|||
: base(tag) |
|||
{ |
|||
} |
|||
|
|||
private ExifLong8Array(ExifLong8Array value) |
|||
: base(value) |
|||
{ |
|||
} |
|||
|
|||
public override ExifDataType DataType |
|||
{ |
|||
get |
|||
{ |
|||
if (this.Value is not null) |
|||
{ |
|||
foreach (ulong value in this.Value) |
|||
{ |
|||
if (value > uint.MaxValue) |
|||
{ |
|||
return ExifDataType.Long8; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return ExifDataType.Long; |
|||
} |
|||
} |
|||
|
|||
public override bool TrySetValue(object value) |
|||
{ |
|||
if (base.TrySetValue(value)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
switch (value) |
|||
{ |
|||
case int val: |
|||
return this.SetSingle((ulong)Numerics.Clamp(val, 0, int.MaxValue)); |
|||
|
|||
case uint val: |
|||
return this.SetSingle((ulong)val); |
|||
|
|||
case short val: |
|||
return this.SetSingle((ulong)Numerics.Clamp(val, 0, short.MaxValue)); |
|||
|
|||
case ushort val: |
|||
return this.SetSingle((ulong)val); |
|||
|
|||
case long val: |
|||
return this.SetSingle((ulong)Numerics.Clamp(val, 0, long.MaxValue)); |
|||
|
|||
case long[] array: |
|||
{ |
|||
if (value.GetType() == typeof(ulong[])) |
|||
{ |
|||
return this.SetArray((ulong[])value); |
|||
} |
|||
|
|||
return this.SetArray(array); |
|||
} |
|||
|
|||
case int[] array: |
|||
{ |
|||
if (value.GetType() == typeof(uint[])) |
|||
{ |
|||
return this.SetArray((uint[])value); |
|||
} |
|||
|
|||
return this.SetArray(array); |
|||
} |
|||
|
|||
case short[] array: |
|||
{ |
|||
if (value.GetType() == typeof(ushort[])) |
|||
{ |
|||
return this.SetArray((ushort[])value); |
|||
} |
|||
|
|||
return this.SetArray(array); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public override IExifValue DeepClone() => new ExifLong8Array(this); |
|||
|
|||
private bool SetSingle(ulong value) |
|||
{ |
|||
this.Value = new[] { value }; |
|||
return true; |
|||
} |
|||
|
|||
private bool SetArray(long[] values) |
|||
{ |
|||
var numbers = new ulong[values.Length]; |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
numbers[i] = (ulong)(values[i] < 0 ? 0 : values[i]); |
|||
} |
|||
|
|||
this.Value = numbers; |
|||
return true; |
|||
} |
|||
|
|||
private bool SetArray(ulong[] values) |
|||
{ |
|||
this.Value = values; |
|||
return true; |
|||
} |
|||
|
|||
private bool SetArray(int[] values) |
|||
{ |
|||
var numbers = new ulong[values.Length]; |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
numbers[i] = (ulong)Numerics.Clamp(values[i], 0, int.MaxValue); |
|||
} |
|||
|
|||
this.Value = numbers; |
|||
return true; |
|||
} |
|||
|
|||
private bool SetArray(uint[] values) |
|||
{ |
|||
var numbers = new ulong[values.Length]; |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
numbers[i] = (ulong)values[i]; |
|||
} |
|||
|
|||
this.Value = numbers; |
|||
return true; |
|||
} |
|||
|
|||
private bool SetArray(short[] values) |
|||
{ |
|||
var numbers = new ulong[values.Length]; |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
numbers[i] = (ulong)Numerics.Clamp(values[i], 0, short.MaxValue); |
|||
} |
|||
|
|||
this.Value = numbers; |
|||
return true; |
|||
} |
|||
|
|||
private bool SetArray(ushort[] values) |
|||
{ |
|||
var numbers = new ulong[values.Length]; |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
numbers[i] = (ulong)values[i]; |
|||
} |
|||
|
|||
this.Value = numbers; |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue