diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 0943ab4eb..ffacf51e4 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -28,7 +28,7 @@
#### **Running tests and Debugging**
-* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/master/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules!
+* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/main/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules!
* Debugging (running tests in Debug mode) is only supported on .NET Core 2.1+, because of JIT Code Generation bugs like [dotnet/coreclr#16443](https://github.com/dotnet/coreclr/issues/16443) or [dotnet/coreclr#20657](https://github.com/dotnet/coreclr/issues/20657)
#### **Do you have questions about consuming the library or the source code?**
diff --git a/.github/ISSUE_TEMPLATE/commercial-bug-report.md b/.github/ISSUE_TEMPLATE/commercial-bug-report.md
deleted file mode 100644
index 024de8e19..000000000
--- a/.github/ISSUE_TEMPLATE/commercial-bug-report.md
+++ /dev/null
@@ -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
-
-
-### Steps to Reproduce
-
-
-### System Configuration
-
-
-- ImageSharp version:
-- Other ImageSharp packages and versions:
-- Environment (Operating system, version and so on):
-- .NET Framework version:
-- Additional information:
-
-
diff --git a/.github/ISSUE_TEMPLATE/commercial-bug-report.yml b/.github/ISSUE_TEMPLATE/commercial-bug-report.yml
new file mode 100644
index 000000000..6b4d914d7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/commercial-bug-report.yml
@@ -0,0 +1,56 @@
+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: checkboxes
+ attributes:
+ label: Prerequisites
+ options:
+ - label: I have bought a Commercial License
+ required: true
+ - label: I have written a descriptive issue title
+ required: true
+ - label: I have verified that I am running the latest version of ImageSharp
+ required: true
+ - label: I have verified if the problem exist in both `DEBUG` and `RELEASE` mode
+ required: true
+ - label: 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
+ required: true
+- 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.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 62a8bf2b4..ec9258883 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request
- url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas
+ url: https://github.com/SixLabors/ImageSharp/discussions/categories/ideas
about: Share ideas for new features for this project.
diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.md b/.github/ISSUE_TEMPLATE/oss-bug-report.md
deleted file mode 100644
index 9e9567a99..000000000
--- a/.github/ISSUE_TEMPLATE/oss-bug-report.md
+++ /dev/null
@@ -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
-
-
-### Steps to Reproduce
-
-
-### System Configuration
-
-
-- ImageSharp version:
-- Other ImageSharp packages and versions:
-- Environment (Operating system, version and so on):
-- .NET Framework version:
-- Additional information:
-
-
diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.yml b/.github/ISSUE_TEMPLATE/oss-bug-report.yml
new file mode 100644
index 000000000..a4e5619d4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/oss-bug-report.yml
@@ -0,0 +1,52 @@
+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: checkboxes
+ attributes:
+ label: Prerequisites
+ options:
+ - label: I have written a descriptive issue title
+ required: true
+ - label: I have verified that I am running the latest version of ImageSharp
+ required: true
+ - label: I have verified if the problem exist in both `DEBUG` and `RELEASE` mode
+ required: true
+ - label: 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
+ required: true
+- 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.
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 6d31e8c53..16f8ebb06 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -3,71 +3,50 @@ name: Build
on:
push:
branches:
- - master
+ - main
tags:
- "v*"
pull_request:
branches:
- - master
+ - main
jobs:
Build:
strategy:
matrix:
options:
- os: ubuntu-latest
- framework: net6.0
- sdk: 6.0.x
+ framework: net7.0
+ sdk: 7.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: macos-latest
- framework: net6.0
- sdk: 6.0.x
+ framework: net7.0
+ sdk: 7.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: windows-latest
- framework: net6.0
- sdk: 6.0.x
+ framework: net7.0
+ sdk: 7.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
+ framework: net6.0
+ sdk: 6.0.x
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
+ framework: net6.0
+ sdk: 6.0.x
runtime: -x64
codecov: false
- os: windows-latest
- framework: net472
+ framework: net6.0
+ sdk: 6.0.x
runtime: -x64
codecov: false
- - os: windows-latest
- framework: net472
- runtime: -x86
- codecov: false
runs-on: ${{matrix.options.os}}
@@ -109,12 +88,13 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget-
- - name: DotNet Setup Preview
- if: ${{ matrix.options.sdk-preview == true }}
+ - name: DotNet Setup
uses: actions/setup-dotnet@v1
with:
- dotnet-version: ${{ matrix.options.sdk }}
include-prerelease: true
+ dotnet-version: |
+ 7.0.x
+ 6.0.x
- name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }}
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index 2b14f2a4b..3f8a82031 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -10,7 +10,7 @@ jobs:
matrix:
options:
- os: ubuntu-latest
- framework: netcoreapp3.1
+ framework: net6.0
runtime: -x64
codecov: true
@@ -54,6 +54,12 @@ jobs:
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
+
- name: DotNet Build
shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}"
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 17d293b43..fbf1ca24b 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -142,6 +142,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gif", "Gif", "{EE3FB0B3-1C3
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{BF8DFDC1-CEE5-4A37-B216-D3085360C776}"
ProjectSection(SolutionItems) = preProject
+ tests\Images\Input\Gif\issues\bugzilla-55918.gif = tests\Images\Input\Gif\issues\bugzilla-55918.gif
+ tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png = tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png
+ tests\Images\Input\Gif\issues\issue1530.gif = tests\Images\Input\Gif\issues\issue1530.gif
+ tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif = tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif
+ tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif = tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif
+ tests\Images\Input\Gif\issues\issue2012_drona1.gif = tests\Images\Input\Gif\issues\issue2012_drona1.gif
+ tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif = tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif
tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif = tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif
tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif
tests\Images\Input\Gif\issues\issue405_badappextlength252.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252.gif
@@ -647,43 +654,25 @@ Global
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
- Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU
Release|Any CPU = Release|Any CPU
- Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
- {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = Release|Any CPU
- {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
- {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
- {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU
- {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
- {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
- {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU
- {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
- {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
- {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU
- {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
- {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/README.md b/README.md
index fdf14b496..6c669fb78 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
-
+
SixLabors.ImageSharp
-[](https://github.com/SixLabors/ImageSharp/actions)
-[](https://codecov.io/gh/SixLabors/ImageSharp)
+[](https://github.com/SixLabors/ImageSharp/actions)
+[](https://codecov.io/gh/SixLabors/ImageSharp)
[](https://opensource.org/licenses/Apache-2.0)
[](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
@@ -26,7 +26,7 @@ Built against [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standa
## License
- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
-- An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
+- An alternative Six Labors License can be purchased **for projects and applications requiring developer support**.
Please visit https://sixlabors.com/pricing for details.
## Support Six Labors
@@ -39,12 +39,13 @@ Support the efforts of the development of the Six Labors projects.
## Documentation
- [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started.
-- Our [Samples Repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) is also available containing buildable code samples demonstrating common activities.
+- Our [Samples Repository](https://github.com/SixLabors/Samples/tree/main/ImageSharp) is also available containing buildable code samples demonstrating common activities.
## Questions
-- Do you have questions? We are happy to help! Please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/category_choices), or ask them on [Stack Overflow](https://stackoverflow.com) using the `ImageSharp` tag. Please do not open issues for questions.
-- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening issues or pull requests!
+- Do you have questions? We are happy to help! Simply purchase a [Six Labors License](https://sixlabors.com/pricing) for developer support. Please do not open issues for questions or misuse our [Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions).
+- For feature ideas please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/categories/ideas) and we'll be happy to discuss.
+- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening issues or pull requests!
## Code of Conduct
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community.
@@ -93,7 +94,7 @@ git submodule update --init --recursive
## How can you help?
-Please... Spread the word, contribute algorithms, submit performance improvements, unit tests, no input is too little. Make sure to read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening a PR.
+Please... Spread the word, contribute algorithms, submit performance improvements, unit tests, no input is too little. Make sure to read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening a PR.
## The ImageSharp Team
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index d211992a9..faa29865f 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -27,6 +27,7 @@
+
diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index 82a146dc7..2323b5ba7 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -287,8 +287,7 @@ namespace SixLabors.ImageSharp.Advanced
where TPixel : unmanaged, IPixel
where TDecoder : class, IImageDecoder
{
- default(TDecoder).Decode(default, default);
- default(TDecoder).DecodeAsync(default, default, default);
+ default(TDecoder).Decode(default, default, default);
}
///
diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
index 1193eccee..8746989b3 100644
--- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp
internal static class StreamExtensions
{
///
- /// Writes data from a stream into the provided buffer.
+ /// Writes data from a stream from the provided buffer.
///
/// The stream.
/// The buffer.
diff --git a/src/ImageSharp/Compression/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs
index 7eb3f4516..1f3cbbca6 100644
--- a/src/ImageSharp/Compression/Zlib/Adler32.cs
+++ b/src/ImageSharp/Compression/Zlib/Adler32.cs
@@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
@@ -31,6 +32,8 @@ namespace SixLabors.ImageSharp.Compression.Zlib
#if SUPPORTS_RUNTIME_INTRINSICS
private const int MinBufferSize = 64;
+ private const int BlockSize = 1 << 5;
+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
private static ReadOnlySpan Tap1Tap2 => new byte[]
{
@@ -63,6 +66,11 @@ namespace SixLabors.ImageSharp.Compression.Zlib
}
#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported && buffer.Length >= MinBufferSize)
+ {
+ return CalculateAvx2(adler, buffer);
+ }
+
if (Ssse3.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateSse(adler, buffer);
@@ -83,19 +91,15 @@ namespace SixLabors.ImageSharp.Compression.Zlib
uint s2 = (adler >> 16) & 0xFFFF;
// Process the data in blocks.
- const int BLOCK_SIZE = 1 << 5;
-
uint length = (uint)buffer.Length;
- uint blocks = length / BLOCK_SIZE;
- length -= blocks * BLOCK_SIZE;
+ uint blocks = length / BlockSize;
+ length -= blocks * BlockSize;
- int index = 0;
- fixed (byte* bufferPtr = buffer)
+ fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
- fixed (byte* tapPtr = Tap1Tap2)
+ fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2))
{
- index += (int)blocks * BLOCK_SIZE;
- var localBufferPtr = bufferPtr;
+ byte* localBufferPtr = bufferPtr;
// _mm_setr_epi8 on x86
Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
@@ -105,7 +109,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
while (blocks > 0)
{
- uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
+ uint n = NMAX / BlockSize; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
@@ -138,7 +142,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
- localBufferPtr += BLOCK_SIZE;
+ localBufferPtr += BlockSize;
}
while (--n > 0);
@@ -164,45 +168,127 @@ namespace SixLabors.ImageSharp.Compression.Zlib
if (length > 0)
{
- if (length >= 16)
- {
- s2 += s1 += localBufferPtr[0];
- s2 += s1 += localBufferPtr[1];
- s2 += s1 += localBufferPtr[2];
- s2 += s1 += localBufferPtr[3];
- s2 += s1 += localBufferPtr[4];
- s2 += s1 += localBufferPtr[5];
- s2 += s1 += localBufferPtr[6];
- s2 += s1 += localBufferPtr[7];
- s2 += s1 += localBufferPtr[8];
- s2 += s1 += localBufferPtr[9];
- s2 += s1 += localBufferPtr[10];
- s2 += s1 += localBufferPtr[11];
- s2 += s1 += localBufferPtr[12];
- s2 += s1 += localBufferPtr[13];
- s2 += s1 += localBufferPtr[14];
- s2 += s1 += localBufferPtr[15];
-
- localBufferPtr += 16;
- length -= 16;
- }
+ HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
+ }
- while (length-- > 0)
- {
- s2 += s1 += *localBufferPtr++;
- }
+ return s1 | (s2 << 16);
+ }
+ }
+ }
- if (s1 >= BASE)
- {
- s1 -= BASE;
- }
+ // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c
+ [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
+ public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer)
+ {
+ uint s1 = adler & 0xFFFF;
+ uint s2 = (adler >> 16) & 0xFFFF;
+ uint length = (uint)buffer.Length;
- s2 %= BASE;
+ fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
+ {
+ byte* localBufferPtr = bufferPtr;
+
+ Vector256 zero = Vector256.Zero;
+ var dot3v = Vector256.Create((short)1);
+ var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
+
+ // Process n blocks of data. At most NMAX data bytes can be
+ // processed before s2 must be reduced modulo BASE.
+ var vs1 = Vector256.CreateScalar(s1);
+ var vs2 = Vector256.CreateScalar(s2);
+
+ while (length >= 32)
+ {
+ int k = length < NMAX ? (int)length : (int)NMAX;
+ k -= k % 32;
+ length -= (uint)k;
+
+ Vector256 vs10 = vs1;
+ Vector256 vs3 = Vector256.Zero;
+
+ while (k >= 32)
+ {
+ // Load 32 input bytes.
+ Vector256 block = Avx.LoadVector256(localBufferPtr);
+
+ // Sum of abs diff, resulting in 2 x int32's
+ Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
+
+ vs1 = Avx2.Add(vs1, vs1sad.AsUInt32());
+ vs3 = Avx2.Add(vs3, vs10);
+
+ // sum 32 uint8s to 16 shorts.
+ Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
+
+ // sum 16 shorts to 8 uint32s.
+ Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
+
+ vs2 = Avx2.Add(vsum2.AsUInt32(), vs2);
+ vs10 = vs1;
+
+ localBufferPtr += BlockSize;
+ k -= 32;
}
- return s1 | (s2 << 16);
+ // Defer the multiplication with 32 to outside of the loop.
+ vs3 = Avx2.ShiftLeftLogical(vs3, 5);
+ vs2 = Avx2.Add(vs2, vs3);
+
+ s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32());
+ s2 = (uint)Numerics.ReduceSum(vs2.AsInt32());
+
+ s1 %= BASE;
+ s2 %= BASE;
+
+ vs1 = Vector256.CreateScalar(s1);
+ vs2 = Vector256.CreateScalar(s2);
}
+
+ if (length > 0)
+ {
+ HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
+ }
+
+ return s1 | (s2 << 16);
+ }
+ }
+
+ private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2)
+ {
+ if (length >= 16)
+ {
+ s2 += s1 += localBufferPtr[0];
+ s2 += s1 += localBufferPtr[1];
+ s2 += s1 += localBufferPtr[2];
+ s2 += s1 += localBufferPtr[3];
+ s2 += s1 += localBufferPtr[4];
+ s2 += s1 += localBufferPtr[5];
+ s2 += s1 += localBufferPtr[6];
+ s2 += s1 += localBufferPtr[7];
+ s2 += s1 += localBufferPtr[8];
+ s2 += s1 += localBufferPtr[9];
+ s2 += s1 += localBufferPtr[10];
+ s2 += s1 += localBufferPtr[11];
+ s2 += s1 += localBufferPtr[12];
+ s2 += s1 += localBufferPtr[13];
+ s2 += s1 += localBufferPtr[14];
+ s2 += s1 += localBufferPtr[15];
+
+ localBufferPtr += 16;
+ length -= 16;
}
+
+ while (length-- > 0)
+ {
+ s2 += s1 += *localBufferPtr++;
+ }
+
+ if (s1 >= BASE)
+ {
+ s1 -= BASE;
+ }
+
+ s2 %= BASE;
}
#endif
diff --git a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
index 506b0f2c1..02fa5bf58 100644
--- a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
@@ -483,18 +483,21 @@ namespace SixLabors.ImageSharp.Compression.Zlib
int niceLength = Math.Min(this.niceLength, this.lookahead);
int matchStrt = this.matchStart;
- this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1);
int matchLength = this.matchLen;
+ matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1);
+ this.matchLen = matchLength;
- if (scan + matchLength > scanMax)
+ if (scan > scanMax - matchLength)
{
return false;
}
+ int scanEndPosition = scan + matchLength;
+
byte* pinnedWindow = this.pinnedWindowPointer;
int scanStart = this.strstart;
- byte scanEnd1 = pinnedWindow[scan + matchLength - 1];
- byte scanEnd = pinnedWindow[scan + matchLength];
+ byte scanEnd1 = pinnedWindow[scanEndPosition - 1];
+ byte scanEnd = pinnedWindow[scanEndPosition];
// Do not waste too much time if we already have a good match:
if (matchLength >= this.goodLength)
@@ -508,8 +511,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib
match = curMatch;
scan = scanStart;
- if (pinnedWindow[match + matchLength] != scanEnd
- || pinnedWindow[match + matchLength - 1] != scanEnd1
+ int matchEndPosition = match + matchLength;
+ if (pinnedWindow[matchEndPosition] != scanEnd
+ || pinnedWindow[matchEndPosition - 1] != scanEnd1
|| pinnedWindow[match] != pinnedWindow[scan]
|| pinnedWindow[++match] != pinnedWindow[++scan])
{
@@ -685,6 +689,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
+ const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD;
while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
{
if (this.lookahead == 0)
@@ -695,7 +700,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
- if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
+ if (this.strstart > windowLen)
{
// slide window, as FindLongestMatch needs this.
// This should only happen when flushing and the window
@@ -766,6 +771,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
+ const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD;
while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
{
if (this.lookahead == 0)
@@ -783,7 +789,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
- if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
+ if (this.strstart >= windowLen)
{
// slide window, as FindLongestMatch needs this.
// This should only happen when flushing and the window
diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
new file mode 100644
index 000000000..89f18cff6
--- /dev/null
+++ b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Threading;
+
+namespace SixLabors.ImageSharp.Diagnostics
+{
+ ///
+ /// Represents the method to handle .
+ ///
+ public delegate void UndisposedAllocationDelegate(string allocationStackTrace);
+
+ ///
+ /// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects.
+ ///
+ public static class MemoryDiagnostics
+ {
+ private static int totalUndisposedAllocationCount;
+
+ private static UndisposedAllocationDelegate undisposedAllocation;
+ private static int undisposedAllocationSubscriptionCounter;
+ private static readonly object SyncRoot = new();
+
+ ///
+ /// 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 .
+ ///
+ public static event UndisposedAllocationDelegate UndisposedAllocation
+ {
+ add
+ {
+ lock (SyncRoot)
+ {
+ undisposedAllocationSubscriptionCounter++;
+ undisposedAllocation += value;
+ }
+ }
+
+ remove
+ {
+ lock (SyncRoot)
+ {
+ undisposedAllocation -= value;
+ undisposedAllocationSubscriptionCounter--;
+ }
+ }
+ }
+
+ ///
+ /// Fires when ImageSharp allocates memory from a MemoryAllocator
+ ///
+ internal static event Action MemoryAllocated;
+
+ ///
+ /// Fires when ImageSharp releases memory allocated from a MemoryAllocator
+ ///
+ internal static event Action MemoryReleased;
+
+ ///
+ /// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
+ ///
+ public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount;
+
+ internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0;
+
+ internal static void IncrementTotalUndisposedAllocationCount()
+ {
+ Interlocked.Increment(ref totalUndisposedAllocationCount);
+ MemoryAllocated?.Invoke();
+ }
+
+ internal static void DecrementTotalUndisposedAllocationCount()
+ {
+ Interlocked.Decrement(ref totalUndisposedAllocationCount);
+ MemoryReleased?.Invoke();
+ }
+
+ 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
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs b/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs
new file mode 100644
index 000000000..864087121
--- /dev/null
+++ b/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Formats.Bmp
+{
+ ///
+ /// Enum for the different color spaces.
+ ///
+ internal enum BmpColorSpace
+ {
+ ///
+ /// This value implies that endpoints and gamma values are given in the appropriate fields.
+ ///
+ LCS_CALIBRATED_RGB = 0,
+
+ ///
+ /// The Windows default color space ('Win ').
+ ///
+ LCS_WINDOWS_COLOR_SPACE = 1466527264,
+
+ ///
+ /// Specifies that the bitmap is in sRGB color space ('sRGB').
+ ///
+ LCS_sRGB = 1934772034,
+
+ ///
+ /// This value indicates that bV5ProfileData points to the file name of the profile to use (gamma and endpoints values are ignored).
+ ///
+ PROFILE_LINKED = 1279872587,
+
+ ///
+ /// This value indicates that bV5ProfileData points to a memory buffer that contains the profile to be used (gamma and endpoints values are ignored).
+ ///
+ PROFILE_EMBEDDED = 1296188740
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
index 129b3a1aa..e76448938 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
@@ -3,9 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -31,48 +28,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
- return decoder.Decode(configuration, stream);
+ return decoder.Decode(configuration, stream, cancellationToken);
}
///
- public Image Decode(Configuration configuration, Stream stream)
- => this.Decode(configuration, stream);
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => this.Decode(configuration, stream, cancellationToken);
///
- public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
- var decoder = new BmpDecoderCore(configuration, this);
- return decoder.DecodeAsync(configuration, stream, cancellationToken);
- }
-
- ///
- public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- => await this.DecodeAsync(configuration, stream, cancellationToken)
- .ConfigureAwait(false);
-
- ///
- public IImageInfo Identify(Configuration configuration, Stream stream)
- {
- Guard.NotNull(stream, nameof(stream));
-
- return new BmpDecoderCore(configuration, this).Identify(configuration, stream);
- }
-
- ///
- public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- {
- Guard.NotNull(stream, nameof(stream));
-
- return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
+ return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 41adc1cff..26687ff16 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -116,17 +117,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets the dimensions of the image.
///
- public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
+ public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
///
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
+ Image image = null;
try
{
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
- var image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
+ image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
Buffer2D pixels = image.GetRootFramePixelBuffer();
@@ -184,7 +186,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
default:
- BmpThrowHelper.ThrowNotSupportedException("Does not support this kind of bitmap files.");
+ BmpThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of bitmap files.");
break;
}
@@ -193,8 +195,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
catch (IndexOutOfRangeException e)
{
+ image?.Dispose();
throw new ImageFormatException("Bitmap does not have a valid format.", e);
}
+ catch
+ {
+ image?.Dispose();
+ throw;
+ }
}
///
@@ -323,12 +331,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4]));
break;
case RleSkippedPixelHandling.Transparent:
- color.FromVector4(Vector4.Zero);
+ color.FromScaledVector4(Vector4.Zero);
break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default:
- color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
+ color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break;
}
}
@@ -382,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
- var yMulWidth = y * width;
+ int yMulWidth = y * width;
int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
{
@@ -395,12 +403,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As(ref bufferSpan[idx]));
break;
case RleSkippedPixelHandling.Transparent:
- color.FromVector4(Vector4.Zero);
+ color.FromScaledVector4(Vector4.Zero);
break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default:
- color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
+ color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break;
}
}
@@ -1127,7 +1135,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
- color.FromVector4(vector4);
+ color.FromScaledVector4(vector4);
}
else
{
@@ -1192,6 +1200,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void ReadInfoHeader()
{
Span buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize];
+ long infoHeaderStart = this.stream.Position;
+
+ // Resolution is stored in PPM.
+ this.metadata = new ImageMetadata
+ {
+ ResolutionUnits = PixelResolutionUnit.PixelsPerMeter
+ };
// Read the header size.
this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize);
@@ -1264,36 +1279,45 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeaderType = BmpInfoHeaderType.Os2Version2;
this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer);
}
- else if (headerSize >= BmpInfoHeader.SizeV4)
+ else if (headerSize == BmpInfoHeader.SizeV4)
{
- // >= 108 bytes
- infoHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5;
+ // == 108 bytes
+ infoHeaderType = BmpInfoHeaderType.WinVersion4;
this.infoHeader = BmpInfoHeader.ParseV4(buffer);
}
+ else if (headerSize > BmpInfoHeader.SizeV4)
+ {
+ // > 108 bytes
+ infoHeaderType = BmpInfoHeaderType.WinVersion5;
+ this.infoHeader = BmpInfoHeader.ParseV5(buffer);
+ if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0)
+ {
+ // Read color profile.
+ long streamPosition = this.stream.Position;
+ byte[] iccProfileData = new byte[this.infoHeader.ProfileSize];
+ this.stream.Position = infoHeaderStart + this.infoHeader.ProfileData;
+ this.stream.Read(iccProfileData);
+ this.metadata.IccProfile = new IccProfile(iccProfileData);
+ this.stream.Position = streamPosition;
+ }
+ }
else
{
BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'.");
}
- // Resolution is stored in PPM.
- var meta = new ImageMetadata
- {
- ResolutionUnits = PixelResolutionUnit.PixelsPerMeter
- };
if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0)
{
- meta.HorizontalResolution = this.infoHeader.XPelsPerMeter;
- meta.VerticalResolution = this.infoHeader.YPelsPerMeter;
+ this.metadata.HorizontalResolution = this.infoHeader.XPelsPerMeter;
+ this.metadata.VerticalResolution = this.infoHeader.YPelsPerMeter;
}
else
{
// Convert default metadata values to PPM.
- meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution));
- meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution));
+ this.metadata.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution));
+ this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution));
}
- this.metadata = meta;
-
short bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
@@ -1363,9 +1387,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int colorMapSizeBytes = -1;
if (this.infoHeader.ClrUsed == 0)
{
- if (this.infoHeader.BitsPerPixel == 1
- || this.infoHeader.BitsPerPixel == 4
- || this.infoHeader.BitsPerPixel == 8)
+ if (this.infoHeader.BitsPerPixel is 1 or 4 or 8)
{
switch (this.fileMarkerType)
{
@@ -1417,7 +1439,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int skipAmount = this.fileHeader.Offset - (int)this.stream.Position;
if ((skipAmount + (int)this.stream.Position) > this.stream.Length)
{
- BmpThrowHelper.ThrowInvalidImageContentException("Invalid fileheader offset found. Offset is greater than the stream length.");
+ BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length.");
}
if (skipAmount > 0)
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 6384074df..247ed7811 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -3,6 +3,7 @@
using System;
using System.Buffers;
+using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
@@ -79,9 +80,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// A bitmap v4 header will only be written, if the user explicitly wants support for transparency.
/// In this case the compression type BITFIELDS will be used.
+ /// If the image contains a color profile, a bitmap v5 header is written, which is needed to write this info.
/// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders.
///
- private readonly bool writeV4Header;
+ private BmpInfoHeaderType infoHeaderType;
///
/// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
@@ -97,8 +99,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
- this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
+ this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
}
///
@@ -123,7 +125,62 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
- // Set Resolution.
+ int colorPaletteSize = 0;
+ if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
+ {
+ colorPaletteSize = ColorPaletteSize8Bit;
+ }
+ else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
+ {
+ colorPaletteSize = ColorPaletteSize4Bit;
+ }
+ else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
+ {
+ colorPaletteSize = ColorPaletteSize1Bit;
+ }
+
+ byte[] iccProfileData = null;
+ int iccProfileSize = 0;
+ if (metadata.IccProfile != null)
+ {
+ this.infoHeaderType = BmpInfoHeaderType.WinVersion5;
+ iccProfileData = metadata.IccProfile.ToByteArray();
+ iccProfileSize = iccProfileData.Length;
+ }
+
+ int infoHeaderSize = this.infoHeaderType switch
+ {
+ BmpInfoHeaderType.WinVersion3 => BmpInfoHeader.SizeV3,
+ BmpInfoHeaderType.WinVersion4 => BmpInfoHeader.SizeV4,
+ BmpInfoHeaderType.WinVersion5 => BmpInfoHeader.SizeV5,
+ _ => BmpInfoHeader.SizeV3
+ };
+
+ BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, image.Height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData);
+
+ Span buffer = stackalloc byte[infoHeaderSize];
+
+ this.WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
+ this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize);
+ this.WriteImage(stream, image.Frames.RootFrame);
+ this.WriteColorProfile(stream, iccProfileData, buffer);
+
+ stream.Flush();
+ }
+
+ ///
+ /// Creates the bitmap information header.
+ ///
+ /// The width of the image.
+ /// The height of the image.
+ /// Size of the information header.
+ /// The bits per pixel.
+ /// The bytes per line.
+ /// The metadata.
+ /// The icc profile data.
+ /// The bitmap information header.
+ private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, short bpp, int bytesPerLine, ImageMetadata metadata, byte[] iccProfileData)
+ {
int hResolution = 0;
int vResolution = 0;
@@ -154,20 +211,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
- int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3;
var infoHeader = new BmpInfoHeader(
headerSize: infoHeaderSize,
- height: image.Height,
- width: image.Width,
+ height: height,
+ width: width,
bitsPerPixel: bpp,
planes: 1,
- imageSize: image.Height * bytesPerLine,
+ imageSize: height * bytesPerLine,
clrUsed: 0,
clrImportant: 0,
xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution);
- if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
+ if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
{
infoHeader.AlphaMask = Rgba32AlphaMask;
infoHeader.RedMask = Rgba32RedMask;
@@ -176,45 +232,79 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields;
}
- int colorPaletteSize = 0;
- if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
+ if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && metadata.IccProfile != null)
{
- colorPaletteSize = ColorPaletteSize8Bit;
+ infoHeader.ProfileSize = iccProfileData.Length;
+ infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED;
+ infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES;
}
- else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
- {
- colorPaletteSize = ColorPaletteSize4Bit;
- }
- else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
+
+ return infoHeader;
+ }
+
+ ///
+ /// Writes the color profile to the stream.
+ ///
+ /// The stream to write to.
+ /// The color profile data.
+ /// The buffer.
+ private void WriteColorProfile(Stream stream, byte[] iccProfileData, Span buffer)
+ {
+ if (iccProfileData != null)
{
- colorPaletteSize = ColorPaletteSize1Bit;
+ // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data.
+ int streamPositionAfterImageData = (int)stream.Position - BmpFileHeader.Size;
+ stream.Write(iccProfileData);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData);
+ stream.Position = BmpFileHeader.Size + 112;
+ stream.Write(buffer.Slice(0, 4));
}
+ }
+ ///
+ /// Writes the bitmap file header.
+ ///
+ /// The stream to write the header to.
+ /// Size of the bitmap information header.
+ /// Size of the color palette.
+ /// The size in bytes of the color profile.
+ /// The information header to write.
+ /// The buffer to write to.
+ private void WriteBitmapFileHeader(Stream stream, int infoHeaderSize, int colorPaletteSize, int iccProfileSize, BmpInfoHeader infoHeader, Span buffer)
+ {
var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap,
- fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + infoHeader.ImageSize,
+ fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader.ImageSize,
reserved: 0,
offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize);
- Span buffer = stackalloc byte[infoHeaderSize];
fileHeader.WriteTo(buffer);
-
stream.Write(buffer, 0, BmpFileHeader.Size);
+ }
- if (this.writeV4Header)
- {
- infoHeader.WriteV4Header(buffer);
- }
- else
+ ///
+ /// Writes the bitmap information header.
+ ///
+ /// The stream to write info header into.
+ /// The information header.
+ /// The buffer.
+ /// Size of the information header.
+ private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span buffer, int infoHeaderSize)
+ {
+ switch (this.infoHeaderType)
{
- infoHeader.WriteV3Header(buffer);
+ case BmpInfoHeaderType.WinVersion3:
+ infoHeader.WriteV3Header(buffer);
+ break;
+ case BmpInfoHeaderType.WinVersion4:
+ infoHeader.WriteV4Header(buffer);
+ break;
+ case BmpInfoHeaderType.WinVersion5:
+ infoHeader.WriteV5Header(buffer);
+ break;
}
stream.Write(buffer, 0, infoHeaderSize);
-
- this.WriteImage(stream, image.Frames.RootFrame);
-
- stream.Flush();
}
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
index acbcdaef3..ab56bd246 100644
--- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
@@ -57,10 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public int Offset { get; }
- public static BmpFileHeader Parse(Span data)
- {
- return MemoryMarshal.Cast(data)[0];
- }
+ public static BmpFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0];
public void WriteTo(Span buffer)
{
diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
index 0d0c05c9f..31394821f 100644
--- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
@@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int greenMask = 0,
int blueMask = 0,
int alphaMask = 0,
- int csType = 0,
+ BmpColorSpace csType = 0,
int redX = 0,
int redY = 0,
int redZ = 0,
@@ -94,7 +94,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int blueZ = 0,
int gammeRed = 0,
int gammeGreen = 0,
- int gammeBlue = 0)
+ int gammeBlue = 0,
+ BmpRenderingIntent intent = BmpRenderingIntent.Invalid,
+ int profileData = 0,
+ int profileSize = 0,
+ int reserved = 0)
{
this.HeaderSize = headerSize;
this.Width = width;
@@ -124,6 +128,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.GammaRed = gammeRed;
this.GammaGreen = gammeGreen;
this.GammaBlue = gammeBlue;
+ this.Intent = intent;
+ this.ProfileData = profileData;
+ this.ProfileSize = profileSize;
+ this.Reserved = reserved;
}
///
@@ -211,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets or sets the Color space type. Not used yet.
///
- public int CsType { get; set; }
+ public BmpColorSpace CsType { get; set; }
///
/// Gets or sets the X coordinate of red endpoint. Not used yet.
@@ -273,21 +281,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public int GammaBlue { get; set; }
+ ///
+ /// Gets or sets the rendering intent for bitmap.
+ ///
+ public BmpRenderingIntent Intent { get; set; }
+
+ ///
+ /// Gets or sets the offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data.
+ ///
+ public int ProfileData { get; set; }
+
+ ///
+ /// Gets or sets the size, in bytes, of embedded profile data.
+ ///
+ public int ProfileSize { get; set; }
+
+ ///
+ /// Gets or sets the reserved value.
+ ///
+ public int Reserved { get; set; }
+
///
/// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
///
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseCore(ReadOnlySpan data)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseCore(ReadOnlySpan data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)),
height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2)));
- }
///
/// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height
@@ -296,15 +321,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
- }
///
/// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
@@ -312,9 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseV3(ReadOnlySpan data)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseV3(ReadOnlySpan data) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
@@ -326,7 +346,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)));
- }
///
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
@@ -336,9 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Indicates, if the alpha bitmask is present.
/// The parsed header.
///
- public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true)
- {
- return new BmpInfoHeader(
+ public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) => new(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
@@ -354,7 +371,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
- }
///
/// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are
@@ -413,11 +429,47 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The data to parse.
/// The parsed header.
///
- public static BmpInfoHeader ParseV4(ReadOnlySpan data)
+ public static BmpInfoHeader ParseV4(ReadOnlySpan data) => new(
+ headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
+ width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
+ height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
+ planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
+ bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
+ compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
+ imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
+ xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
+ yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
+ clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
+ clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)),
+ redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)),
+ greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
+ blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
+ alphaMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)),
+ csType: (BmpColorSpace)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(56, 4)),
+ redX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(60, 4)),
+ redY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(64, 4)),
+ redZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(68, 4)),
+ greenX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(72, 4)),
+ greenY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(76, 4)),
+ greenZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(80, 4)),
+ blueX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(84, 4)),
+ blueY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(88, 4)),
+ blueZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(92, 4)),
+ gammeRed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(96, 4)),
+ gammeGreen: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(100, 4)),
+ gammeBlue: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(104, 4)));
+
+ ///
+ /// Parses the full BMP Version 5 BITMAPINFOHEADER header (124 bytes).
+ ///
+ /// The data to parse.
+ /// The parsed header.
+ ///
+ public static BmpInfoHeader ParseV5(ReadOnlySpan data)
{
- if (data.Length < SizeV4)
+ if (data.Length < SizeV5)
{
- throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes.");
+ throw new ArgumentException(nameof(data), $"Must be {SizeV5} bytes. Was {data.Length} bytes.");
}
return MemoryMarshal.Cast(data)[0];
@@ -448,6 +500,43 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// The buffer to write to.
public void WriteV4Header(Span buffer)
+ {
+ buffer.Clear();
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), SizeV4);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height);
+ BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes);
+ BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(40, 4), this.RedMask);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(44, 4), this.GreenMask);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(48, 4), this.BlueMask);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(52, 4), this.AlphaMask);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(56, 4), (int)this.CsType);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(60, 4), this.RedX);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(64, 4), this.RedY);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(68, 4), this.RedZ);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(72, 4), this.GreenX);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(76, 4), this.GreenY);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(80, 4), this.GreenZ);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(84, 4), this.BlueX);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(88, 4), this.BlueY);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(92, 4), this.BlueZ);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(96, 4), this.GammaRed);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(100, 4), this.GammaGreen);
+ BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(104, 4), this.GammaBlue);
+ }
+
+ ///
+ /// Writes a complete Bitmap V5 header to a buffer.
+ ///
+ /// The buffer to write to.
+ public void WriteV5Header(Span buffer)
{
ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer));
diff --git a/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs b/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs
new file mode 100644
index 000000000..e437a0cbf
--- /dev/null
+++ b/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Formats.Bmp
+{
+ ///
+ /// Enum for the different rendering intent's.
+ ///
+ internal enum BmpRenderingIntent
+ {
+ ///
+ /// Invalid default value.
+ ///
+ Invalid = 0,
+
+ ///
+ /// Maintains saturation. Used for business charts and other situations in which undithered colors are required.
+ ///
+ LCS_GM_BUSINESS = 1,
+
+ ///
+ /// Maintains colorimetric match. Used for graphic designs and named colors.
+ ///
+ LCS_GM_GRAPHICS = 2,
+
+ ///
+ /// Maintains contrast. Used for photographs and natural images.
+ ///
+ LCS_GM_IMAGES = 4,
+
+ ///
+ /// Maintains the white point. Matches the colors to their nearest color in the destination gamut.
+ ///
+ LCS_GM_ABS_COLORIMETRIC = 8,
+ }
+}
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index 196d77ad7..6d6cfc079 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -3,9 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -27,48 +24,24 @@ namespace SixLabors.ImageSharp.Formats.Gif
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
var decoder = new GifDecoderCore(configuration, this);
- return decoder.Decode(configuration, stream);
+ return decoder.Decode(configuration, stream, cancellationToken);
}
///
- public Image Decode(Configuration configuration, Stream stream)
- => this.Decode(configuration, stream);
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => this.Decode(configuration, stream, cancellationToken);
///
- public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- var decoder = new GifDecoderCore(configuration, this);
- return decoder.DecodeAsync(configuration, stream, cancellationToken);
- }
-
- ///
- public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- => await this.DecodeAsync(configuration, stream, cancellationToken)
- .ConfigureAwait(false);
-
- ///
- public IImageInfo Identify(Configuration configuration, Stream stream)
- {
- Guard.NotNull(stream, nameof(stream));
-
- var decoder = new GifDecoderCore(configuration, this);
-
- using var bufferedStream = new BufferedReadStream(configuration, stream);
- return decoder.Identify(bufferedStream, default);
- }
-
- ///
- public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
- return decoder.IdentifyAsync(configuration, stream, cancellationToken);
+ return decoder.Identify(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index b6348803a..d17e89cd4 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -221,7 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private void ReadGraphicalControlExtension()
{
- this.stream.Read(this.buffer, 0, 6);
+ int bytesRead = this.stream.Read(this.buffer, 0, 6);
+ if (bytesRead != 6)
+ {
+ GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
+ }
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
}
@@ -231,7 +235,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private void ReadImageDescriptor()
{
- this.stream.Read(this.buffer, 0, 9);
+ int bytesRead = this.stream.Read(this.buffer, 0, 9);
+ if (bytesRead != 9)
+ {
+ GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
+ }
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
@@ -245,7 +253,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private void ReadLogicalScreenDescriptor()
{
- this.stream.Read(this.buffer, 0, 7);
+ int bytesRead = this.stream.Read(this.buffer, 0, 7);
+ if (bytesRead != 7)
+ {
+ GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
+ }
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
}
@@ -265,10 +277,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
- if (isXmp)
+ if (isXmp && !this.IgnoreMetadata)
{
- var extension = GifXmpApplicationExtension.Read(this.stream);
- this.metadata.XmpProfile = new XmpProfile(extension.Data);
+ var extension = GifXmpApplicationExtension.Read(this.stream, this.MemoryAllocator);
+ if (extension.Data.Length > 0)
+ {
+ this.metadata.XmpProfile = new XmpProfile(extension.Data);
+ }
+
return;
}
else
@@ -374,9 +390,19 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
-
this.ReadFrameIndices(indices);
- ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan());
+
+ Span rawColorTable = default;
+ if (localColorTable != null)
+ {
+ rawColorTable = localColorTable.GetSpan();
+ }
+ else if (this.globalColorTable != null)
+ {
+ rawColorTable = this.globalColorTable.GetSpan();
+ }
+
+ ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable);
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
// Skip any remaining blocks
@@ -396,9 +422,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(Buffer2D indices)
{
- int dataSize = this.stream.ReadByte();
+ int minCodeSize = this.stream.ReadByte();
using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream);
- lzwDecoder.DecodePixels(dataSize, indices);
+ lzwDecoder.DecodePixels(minCodeSize, indices);
}
///
@@ -415,6 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height;
+ bool transFlag = this.graphicsControlExtension.TransparencyFlag;
ImageFrame prevFrame = null;
ImageFrame currentFrame = null;
@@ -422,8 +449,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (previousFrame is null)
{
- // This initializes the image to become fully transparent because the alpha channel is zero.
- image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata);
+ if (!transFlag)
+ {
+ image = new Image(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata);
+ }
+ else
+ {
+ // This initializes the image to become fully transparent because the alpha channel is zero.
+ image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata);
+ }
this.SetFrameMetadata(image.Frames.RootFrame.Metadata);
@@ -445,6 +479,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.RestoreToBackground(imageFrame);
}
+ if (colorTable.Length == 0)
+ {
+ return;
+ }
+
int interlacePass = 0; // The interlace pass
int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line
@@ -452,7 +491,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
int descriptorBottom = descriptorTop + descriptor.Height;
int descriptorLeft = descriptor.Left;
int descriptorRight = descriptorLeft + descriptor.Width;
- bool transFlag = this.graphicsControlExtension.TransparencyFlag;
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
int colorTableMaxIdx = colorTable.Length - 1;
@@ -635,10 +673,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.gifMetadata.GlobalColorTableLength = globalColorTableLength;
- this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean);
+ if (globalColorTableLength > 0)
+ {
+ this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean);
- // Read the global color table data from the stream
- stream.Read(this.globalColorTable.GetSpan());
+ // Read the global color table data from the stream
+ stream.Read(this.globalColorTable.GetSpan());
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index a21b050a8..da5b1cb23 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -123,7 +123,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.WriteComments(gifMetadata, stream);
// Write application extensions.
- this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, metadata.XmpProfile);
+ XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
+ this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
if (useGlobalTable)
{
@@ -137,7 +138,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Clean up.
quantized.Dispose();
- // TODO: Write extension etc
stream.WriteByte(GifConstants.EndIntroducer);
}
@@ -428,26 +428,31 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TGifExtension : struct, IGifExtension
{
IMemoryOwner owner = null;
- Span buffer;
+ Span extensionBuffer;
int extensionSize = extension.ContentLength;
- if (extensionSize > this.buffer.Length - 3)
+
+ if (extensionSize == 0)
+ {
+ return;
+ }
+ else if (extensionSize > this.buffer.Length - 3)
{
owner = this.memoryAllocator.Allocate(extensionSize + 3);
- buffer = owner.GetSpan();
+ extensionBuffer = owner.GetSpan();
}
else
{
- buffer = this.buffer;
+ extensionBuffer = this.buffer;
}
- buffer[0] = GifConstants.ExtensionIntroducer;
- buffer[1] = extension.Label;
+ extensionBuffer[0] = GifConstants.ExtensionIntroducer;
+ extensionBuffer[1] = extension.Label;
- extension.WriteTo(buffer.Slice(2));
+ extension.WriteTo(extensionBuffer.Slice(2));
- buffer[extensionSize + 2] = GifConstants.Terminator;
+ extensionBuffer[extensionSize + 2] = GifConstants.Terminator;
- stream.Write(buffer, 0, extensionSize + 3);
+ stream.Write(extensionBuffer, 0, extensionSize + 3);
owner?.Dispose();
}
diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
index 68227db53..2a0720001 100644
--- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
@@ -64,21 +64,30 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Decodes and decompresses all pixel indices from the stream.
///
- /// Size of the data.
+ /// Minimum code size of the data.
/// The pixel array to decode to.
- public void DecodePixels(int dataSize, Buffer2D pixels)
+ public void DecodePixels(int minCodeSize, Buffer2D pixels)
{
- Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
+ // Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
+ int clearCode = 1 << minCodeSize;
+
+ // It is possible to specify a larger LZW minimum code size than the palette length in bits
+ // which may leave a gap in the codes where no colors are assigned.
+ // http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
+ if (minCodeSize < 2 || clearCode > MaxStackSize)
+ {
+ // Don't attempt to decode the frame indices.
+ // Theoretically we could determine a min code size from the length of the provided
+ // color palette but we won't bother since the image is most likely corrupted.
+ GifThrowHelper.ThrowInvalidImageContentException("Gif Image does not contain a valid LZW minimum code.");
+ }
// The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height;
- // Calculate the clear code. The value of the clear code is 2 ^ dataSize
- int clearCode = 1 << dataSize;
-
- int codeSize = dataSize + 1;
+ int codeSize = minCodeSize + 1;
// Calculate the end code
int endCode = clearCode + 1;
@@ -165,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (code == clearCode)
{
// Reset the decoder
- codeSize = dataSize + 1;
+ codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
index 801849c9b..847633694 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
@@ -71,13 +71,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
dest = this;
- return 5;
+ return ((IGifExtension)this).ContentLength;
}
public static GifGraphicControlExtension Parse(ReadOnlySpan buffer)
- {
- return MemoryMarshal.Cast(buffer)[0];
- }
+ => MemoryMarshal.Cast(buffer)[0];
public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
{
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
index 2c7bed611..c9e8033db 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
@@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// 0 means loop indefinitely. Count is set as play n + 1 times.
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount);
- return 16; // Length - Introducer + Label + Terminator.
+ return this.ContentLength; // Length - Introducer + Label + Terminator.
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
index 236508fe9..8c396e7fb 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
@@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Collections.Generic;
using System.IO;
-using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif
{
@@ -14,7 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
public byte Label => GifConstants.ApplicationExtensionLabel;
- public int ContentLength => this.Data.Length + 269; // 12 + Data Length + 1 + 256
+ // size : 1
+ // identifier : 11
+ // magic trailer : 257
+ public int ContentLength => (this.Data.Length > 0) ? this.Data.Length + 269 : 0;
///
/// Gets the raw Data.
@@ -25,51 +28,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Reads the XMP metadata from the specified stream.
///
/// The stream to read from.
+ /// The memory allocator.
/// The XMP metadata
/// Thrown if the XMP block is not properly terminated.
- public static GifXmpApplicationExtension Read(Stream stream)
+ public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator)
{
- // 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();
- 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);
- }
+ byte[] xmpBytes = ReadXmpData(stream, allocator);
- // 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 bufferSpan = buffer;
- int pos = 0;
- for (int j = 0; j < list.Count - 1; j++)
+ // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
+ int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0
+ byte[] buffer = Array.Empty();
+ if (xmpLength > 0)
{
- list[j].CopyTo(bufferSpan.Slice(pos));
- pos += bufferSize;
+ buffer = new byte[xmpLength];
+ xmpBytes.AsSpan(0, xmpLength).CopyTo(buffer);
+ stream.Skip(1); // Skip the terminator.
}
- // Last one only needs the portion until terminationIndex copied over.
- Span 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 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;
@@ -91,7 +71,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
buffer[bytesWritten++] = 0x00;
- return totalSize;
+ return this.ContentLength;
+ }
+
+ private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator)
+ {
+ using ChunkedMemoryStream bytes = new(allocator);
+
+ // XMP data doesn't have a fixed length nor is there an indicator of the length.
+ // So we simply read one byte at a time until we hit the 0x0 value at the end
+ // of the magic trailer or the end of the stream.
+ // Using ChunkedMemoryStream reduces the array resize allocation normally associated
+ // with writing from a non fixed-size buffer.
+ while (true)
+ {
+ int b = stream.ReadByte();
+ if (b <= 0)
+ {
+ return bytes.ToArray();
+ }
+
+ bytes.WriteByte((byte)b);
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs
index b55f1119b..db7f64ee2 100644
--- a/src/ImageSharp/Formats/IImageDecoder.cs
+++ b/src/ImageSharp/Formats/IImageDecoder.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@@ -13,26 +12,6 @@ namespace SixLabors.ImageSharp.Formats
///
public interface IImageDecoder
{
- ///
- /// Decodes the image from the specified stream to an of a specific pixel type.
- ///
- /// The pixel format.
- /// The configuration for the image.
- /// The containing image data.
- /// The .
- // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
- Image Decode(Configuration configuration, Stream stream)
- where TPixel : unmanaged, IPixel;
-
- ///
- /// Decodes the image from the specified stream to an .
- ///
- /// The configuration for the image.
- /// The containing image data.
- /// The .
- // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
- Image Decode(Configuration configuration, Stream stream);
-
///
/// Decodes the image from the specified stream to an of a specific pixel type.
///
@@ -42,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats
/// The token to monitor for cancellation requests.
/// The .
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
- Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel;
///
@@ -53,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats
/// The token to monitor for cancellation requests.
/// The .
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
- Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
+ Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs
index 6f5fc2333..c6377edd0 100644
--- a/src/ImageSharp/Formats/IImageInfoDetector.cs
+++ b/src/ImageSharp/Formats/IImageInfoDetector.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats
{
@@ -12,14 +11,6 @@ namespace SixLabors.ImageSharp.Formats
///
public interface IImageInfoDetector
{
- ///
- /// Reads the raw image information from the specified stream.
- ///
- /// The configuration for the image.
- /// The containing image data.
- /// The object
- IImageInfo Identify(Configuration configuration, Stream stream);
-
///
/// Reads the raw image information from the specified stream.
///
@@ -27,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats
/// The containing image data.
/// The token to monitor for cancellation requests.
/// The object
- Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
+ IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}
diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
index 5d77fb0c8..71ecda893 100644
--- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs
+++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
@@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -13,153 +12,45 @@ namespace SixLabors.ImageSharp.Formats
{
internal static class ImageDecoderUtilities
{
- ///
- /// Reads the raw image information from the specified stream.
- ///
- /// The decoder.
- /// /// The configuration for the image.
- /// The containing image data.
- /// The token to monitor for cancellation requests.
- /// is null.
- /// A representing the asynchronous operation.
- public static Task IdentifyAsync(
- this IImageDecoderInternals decoder,
- Configuration configuration,
- Stream stream,
- CancellationToken cancellationToken)
- => decoder.IdentifyAsync(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
-
- ///
- /// Reads the raw image information from the specified stream.
- ///
- /// The decoder.
- /// The configuration for the image.
- /// The containing image data.
- /// Factory method to handle as .
- /// The token to monitor for cancellation requests.
- /// is null.
- /// A representing the asynchronous operation.
- public static Task IdentifyAsync(
+ public static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
- Func tooLargeImageExceptionFactory,
CancellationToken cancellationToken)
{
+ using var bufferedReadStream = new BufferedReadStream(configuration, stream);
+
try
{
- using var bufferedReadStream = new BufferedReadStream(configuration, stream);
- IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken);
- return Task.FromResult(imageInfo);
+ return decoder.Identify(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
- InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions);
- return Task.FromException(invalidImageContentException);
- }
- catch (OperationCanceledException)
- {
- return Task.FromCanceled(cancellationToken);
- }
- catch (Exception ex)
- {
- return Task.FromException(ex);
+ throw new InvalidImageContentException(decoder.Dimensions, ex);
}
}
- ///
- /// Decodes the image from the specified stream.
- ///
- /// The pixel format.
- /// The decoder.
- /// The configuration for the image.
- /// The containing image data.
- /// The token to monitor for cancellation requests.
- /// A representing the asynchronous operation.
- public static Task> DecodeAsync(
- this IImageDecoderInternals decoder,
- Configuration configuration,
- Stream stream,
- CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel =>
- decoder.DecodeAsync(
- configuration,
- stream,
- DefaultLargeImageExceptionFactory,
- cancellationToken);
-
- ///
- /// Decodes the image from the specified stream.
- ///
- /// The pixel format.
- /// The decoder.
- /// The configuration for the image.
- /// The containing image data.
- /// Factory method to handle as .
- /// The token to monitor for cancellation requests.
- /// A representing the asynchronous operation.
- public static Task> DecodeAsync(
+ public static Image Decode(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
- Func largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
- {
- try
- {
- using var bufferedReadStream = new BufferedReadStream(configuration, stream);
- Image image = decoder.Decode(bufferedReadStream, cancellationToken);
- return Task.FromResult(image);
- }
- catch (InvalidMemoryOperationException ex)
- {
- InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions);
- return Task.FromException>(invalidImageContentException);
- }
- catch (OperationCanceledException)
- {
- return Task.FromCanceled>(cancellationToken);
- }
- catch (Exception ex)
- {
- return Task.FromException>(ex);
- }
- }
-
- public static IImageInfo Identify(
- this IImageDecoderInternals decoder,
- Configuration configuration,
- Stream stream)
- {
- using var bufferedReadStream = new BufferedReadStream(configuration, stream);
-
- try
- {
- return decoder.Identify(bufferedReadStream, default);
- }
- catch (InvalidMemoryOperationException ex)
- {
- throw new InvalidImageContentException(decoder.Dimensions, ex);
- }
- }
-
- public static Image Decode(this IImageDecoderInternals decoder, Configuration configuration, Stream stream)
- where TPixel : unmanaged, IPixel
- => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory);
+ => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
public static Image Decode(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
- Func largeImageExceptionFactory)
+ Func largeImageExceptionFactory,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
- return decoder.Decode(bufferedReadStream, default);
+ return decoder.Decode(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
@@ -170,6 +61,6 @@ namespace SixLabors.ImageSharp.Formats
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
- new InvalidImageContentException(dimensions, memoryOperationException);
+ new(dimensions, memoryOperationException);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs b/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs
new file mode 100644
index 000000000..ff3c0539c
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components
+{
+ internal enum ComponentType
+ {
+ Huffman = 0,
+
+ Arithmetic = 1
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs
new file mode 100644
index 000000000..a2736900f
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal class ArithmeticDecodingComponent : JpegComponent
+ {
+ public ArithmeticDecodingComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index)
+ : base(memoryAllocator, frame, id, horizontalFactor, verticalFactor, quantizationTableIndex, index)
+ {
+ }
+
+ ///
+ /// Gets or sets the dc context.
+ ///
+ public int DcContext { get; set; }
+
+ ///
+ /// Gets or sets the dc statistics.
+ ///
+ public ArithmeticStatistics DcStatistics { get; set; }
+
+ ///
+ /// Gets or sets the ac statistics.
+ ///
+ public ArithmeticStatistics AcStatistics { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs
new file mode 100644
index 000000000..6055f300d
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal class ArithmeticDecodingTable
+ {
+ public ArithmeticDecodingTable(byte tableClass, byte identifier)
+ {
+ this.TableClass = tableClass;
+ this.Identifier = identifier;
+ }
+
+ public byte TableClass { get; }
+
+ public byte Identifier { get; }
+
+ public byte ConditioningTableValue { get; private set; }
+
+ public int DcL { get; private set; }
+
+ public int DcU { get; private set; }
+
+ public int AcKx { get; private set; }
+
+ public void Configure(byte conditioningTableValue)
+ {
+ this.ConditioningTableValue = conditioningTableValue;
+ if (this.TableClass == 0)
+ {
+ this.DcL = conditioningTableValue & 0x0F;
+ this.DcU = conditioningTableValue >> 4;
+ this.AcKx = 0;
+ }
+ else
+ {
+ this.DcL = 0;
+ this.DcU = 0;
+ this.AcKx = conditioningTableValue;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
new file mode 100644
index 000000000..d3a5ea15b
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
@@ -0,0 +1,1238 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using SixLabors.ImageSharp.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ ///
+ /// Decodes a arithmetic encoded spectral scan.
+ /// Based on https://github.com/yigolden/JpegLibrary/blob/main/src/JpegLibrary/ScanDecoder/JpegArithmeticScanDecoder.cs
+ ///
+ internal class ArithmeticScanDecoder : IJpegScanDecoder
+ {
+ private readonly BufferedReadStream stream;
+
+ private int c;
+ private int a;
+ private int ct;
+
+ ///
+ /// instance containing decoding-related information.
+ ///
+ private JpegFrame frame;
+
+ ///
+ /// Shortcut for .Components.
+ ///
+ private IJpegComponent[] components;
+
+ ///
+ /// Number of component in the current scan.
+ ///
+ private int scanComponentCount;
+
+ ///
+ /// The reset interval determined by RST markers.
+ ///
+ private int restartInterval;
+
+ ///
+ /// How many mcu's are left to do.
+ ///
+ private int todo;
+
+ private readonly SpectralConverter spectralConverter;
+
+ private JpegBitReader scanBuffer;
+
+ private ArithmeticDecodingTable[] dcDecodingTables;
+
+ private ArithmeticDecodingTable[] acDecodingTables;
+
+ private readonly byte[] fixedBin = { 113, 0, 0, 0 };
+
+ private readonly CancellationToken cancellationToken;
+
+ private static readonly int[] ArithmeticTable =
+ {
+ Pack(0x5a1d, 1, 1, 1),
+ Pack(0x2586, 14, 2, 0),
+ Pack(0x1114, 16, 3, 0),
+ Pack(0x080b, 18, 4, 0),
+ Pack(0x03d8, 20, 5, 0),
+ Pack(0x01da, 23, 6, 0),
+ Pack(0x00e5, 25, 7, 0),
+ Pack(0x006f, 28, 8, 0),
+ Pack(0x0036, 30, 9, 0),
+ Pack(0x001a, 33, 10, 0),
+ Pack(0x000d, 35, 11, 0),
+ Pack(0x0006, 9, 12, 0),
+ Pack(0x0003, 10, 13, 0),
+ Pack(0x0001, 12, 13, 0),
+ Pack(0x5a7f, 15, 15, 1),
+ Pack(0x3f25, 36, 16, 0),
+ Pack(0x2cf2, 38, 17, 0),
+ Pack(0x207c, 39, 18, 0),
+ Pack(0x17b9, 40, 19, 0),
+ Pack(0x1182, 42, 20, 0),
+ Pack(0x0cef, 43, 21, 0),
+ Pack(0x09a1, 45, 22, 0),
+ Pack(0x072f, 46, 23, 0),
+ Pack(0x055c, 48, 24, 0),
+ Pack(0x0406, 49, 25, 0),
+ Pack(0x0303, 51, 26, 0),
+ Pack(0x0240, 52, 27, 0),
+ Pack(0x01b1, 54, 28, 0),
+ Pack(0x0144, 56, 29, 0),
+ Pack(0x00f5, 57, 30, 0),
+ Pack(0x00b7, 59, 31, 0),
+ Pack(0x008a, 60, 32, 0),
+ Pack(0x0068, 62, 33, 0),
+ Pack(0x004e, 63, 34, 0),
+ Pack(0x003b, 32, 35, 0),
+ Pack(0x002c, 33, 9, 0),
+ Pack(0x5ae1, 37, 37, 1),
+ Pack(0x484c, 64, 38, 0),
+ Pack(0x3a0d, 65, 39, 0),
+ Pack(0x2ef1, 67, 40, 0),
+ Pack(0x261f, 68, 41, 0),
+ Pack(0x1f33, 69, 42, 0),
+ Pack(0x19a8, 70, 43, 0),
+ Pack(0x1518, 72, 44, 0),
+ Pack(0x1177, 73, 45, 0),
+ Pack(0x0e74, 74, 46, 0),
+ Pack(0x0bfb, 75, 47, 0),
+ Pack(0x09f8, 77, 48, 0),
+ Pack(0x0861, 78, 49, 0),
+ Pack(0x0706, 79, 50, 0),
+ Pack(0x05cd, 48, 51, 0),
+ Pack(0x04de, 50, 52, 0),
+ Pack(0x040f, 50, 53, 0),
+ Pack(0x0363, 51, 54, 0),
+ Pack(0x02d4, 52, 55, 0),
+ Pack(0x025c, 53, 56, 0),
+ Pack(0x01f8, 54, 57, 0),
+ Pack(0x01a4, 55, 58, 0),
+ Pack(0x0160, 56, 59, 0),
+ Pack(0x0125, 57, 60, 0),
+ Pack(0x00f6, 58, 61, 0),
+ Pack(0x00cb, 59, 62, 0),
+ Pack(0x00ab, 61, 63, 0),
+ Pack(0x008f, 61, 32, 0),
+ Pack(0x5b12, 65, 65, 1),
+ Pack(0x4d04, 80, 66, 0),
+ Pack(0x412c, 81, 67, 0),
+ Pack(0x37d8, 82, 68, 0),
+ Pack(0x2fe8, 83, 69, 0),
+ Pack(0x293c, 84, 70, 0),
+ Pack(0x2379, 86, 71, 0),
+ Pack(0x1edf, 87, 72, 0),
+ Pack(0x1aa9, 87, 73, 0),
+ Pack(0x174e, 72, 74, 0),
+ Pack(0x1424, 72, 75, 0),
+ Pack(0x119c, 74, 76, 0),
+ Pack(0x0f6b, 74, 77, 0),
+ Pack(0x0d51, 75, 78, 0),
+ Pack(0x0bb6, 77, 79, 0),
+ Pack(0x0a40, 77, 48, 0),
+ Pack(0x5832, 80, 81, 1),
+ Pack(0x4d1c, 88, 82, 0),
+ Pack(0x438e, 89, 83, 0),
+ Pack(0x3bdd, 90, 84, 0),
+ Pack(0x34ee, 91, 85, 0),
+ Pack(0x2eae, 92, 86, 0),
+ Pack(0x299a, 93, 87, 0),
+ Pack(0x2516, 86, 71, 0),
+ Pack(0x5570, 88, 89, 1),
+ Pack(0x4ca9, 95, 90, 0),
+ Pack(0x44d9, 96, 91, 0),
+ Pack(0x3e22, 97, 92, 0),
+ Pack(0x3824, 99, 93, 0),
+ Pack(0x32b4, 99, 94, 0),
+ Pack(0x2e17, 93, 86, 0),
+ Pack(0x56a8, 95, 96, 1),
+ Pack(0x4f46, 101, 97, 0),
+ Pack(0x47e5, 102, 98, 0),
+ Pack(0x41cf, 103, 99, 0),
+ Pack(0x3c3d, 104, 100, 0),
+ Pack(0x375e, 99, 93, 0),
+ Pack(0x5231, 105, 102, 0),
+ Pack(0x4c0f, 106, 103, 0),
+ Pack(0x4639, 107, 104, 0),
+ Pack(0x415e, 103, 99, 0),
+ Pack(0x5627, 105, 106, 1),
+ Pack(0x50e7, 108, 107, 0),
+ Pack(0x4b85, 109, 103, 0),
+ Pack(0x5597, 110, 109, 0),
+ Pack(0x504f, 111, 107, 0),
+ Pack(0x5a10, 110, 111, 1),
+ Pack(0x5522, 112, 109, 0),
+ Pack(0x59eb, 112, 111, 1),
+
+ // This last entry is used for fixed probability estimate of 0.5
+ // as suggested in Section 10.3 Table 5 of ITU-T Rec. T.851.
+ Pack(0x5a1d, 113, 113, 0)
+ };
+
+ private readonly List statistics = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream.
+ /// Spectral to pixel converter.
+ /// The token to monitor cancellation.
+ public ArithmeticScanDecoder(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken)
+ {
+ this.stream = stream;
+ this.spectralConverter = converter;
+ this.cancellationToken = cancellationToken;
+
+ this.c = 0;
+ this.a = 0;
+ this.ct = -16; // Force reading 2 initial bytes to fill C.
+ }
+
+ ///
+ public int ResetInterval
+ {
+ set
+ {
+ this.restartInterval = value;
+ this.todo = value;
+ }
+ }
+
+ ///
+ public int SpectralStart { get; set; }
+
+ ///
+ public int SpectralEnd { get; set; }
+
+ ///
+ public int SuccessiveHigh { get; set; }
+
+ ///
+ public int SuccessiveLow { get; set; }
+
+ public void InitDecodingTables(List arithmeticDecodingTables)
+ {
+ for (int i = 0; i < this.components.Length; i++)
+ {
+ var component = this.components[i] as ArithmeticDecodingComponent;
+ this.dcDecodingTables[i] = this.GetArithmeticTable(arithmeticDecodingTables, true, component.DcTableId);
+ component.DcStatistics = this.CreateOrGetStatisticsBin(true, component.DcTableId);
+ this.acDecodingTables[i] = this.GetArithmeticTable(arithmeticDecodingTables, false, component.AcTableId);
+ component.AcStatistics = this.CreateOrGetStatisticsBin(false, component.AcTableId);
+ }
+ }
+
+ private ref byte GetFixedBinReference() => ref this.fixedBin[0];
+
+ ///
+ /// Decodes the entropy coded data.
+ ///
+ /// Component count in the current scan.
+ public void ParseEntropyCodedData(int scanComponentCount)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+
+ this.scanComponentCount = scanComponentCount;
+
+ this.scanBuffer = new JpegBitReader(this.stream);
+
+ bool fullScan = this.frame.Progressive || this.frame.MultiScan;
+ this.frame.AllocateComponents(fullScan);
+
+ if (this.frame.Progressive)
+ {
+ this.ParseProgressiveData();
+ }
+ else
+ {
+ this.ParseBaselineData();
+ }
+
+ if (this.scanBuffer.HasBadMarker())
+ {
+ this.stream.Position = this.scanBuffer.MarkerPosition;
+ }
+ }
+
+ ///
+ public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
+ {
+ this.frame = frame;
+ this.components = frame.Components;
+
+ this.dcDecodingTables = new ArithmeticDecodingTable[this.components.Length];
+ this.acDecodingTables = new ArithmeticDecodingTable[this.components.Length];
+
+ this.spectralConverter.InjectFrameData(frame, jpegData);
+ }
+
+ private ArithmeticDecodingTable GetArithmeticTable(List arithmeticDecodingTables, bool isDcTable, int identifier)
+ {
+ int tableClass = isDcTable ? 0 : 1;
+
+ foreach (ArithmeticDecodingTable item in arithmeticDecodingTables)
+ {
+ if (item.TableClass == tableClass && item.Identifier == identifier)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ private ArithmeticStatistics CreateOrGetStatisticsBin(bool dc, int identifier, bool reset = false)
+ {
+ foreach (ArithmeticStatistics item in this.statistics)
+ {
+ if (item.IsDcStatistics == dc && item.Identifier == identifier)
+ {
+ if (reset)
+ {
+ item.Reset();
+ }
+
+ return item;
+ }
+ }
+
+ var statistic = new ArithmeticStatistics(dc, identifier);
+ this.statistics.Add(statistic);
+ return statistic;
+ }
+
+ private void ParseBaselineData()
+ {
+ foreach (ArithmeticDecodingComponent component in this.components)
+ {
+ component.DcPredictor = 0;
+ component.DcContext = 0;
+ component.DcStatistics?.Reset();
+ component.AcStatistics?.Reset();
+ }
+
+ this.Reset();
+
+ if (this.scanComponentCount != 1)
+ {
+ this.ParseBaselineDataInterleaved();
+ this.spectralConverter.CommitConversion();
+ }
+ else if (this.frame.ComponentCount == 1)
+ {
+ this.ParseBaselineDataSingleComponent();
+ this.spectralConverter.CommitConversion();
+ }
+ else
+ {
+ this.ParseBaselineDataNonInterleaved();
+ }
+ }
+
+ private void ParseProgressiveData()
+ {
+ this.CheckProgressiveData();
+
+ foreach (ArithmeticDecodingComponent component in this.components)
+ {
+ if (this.SpectralStart == 0 && this.SuccessiveHigh == 0)
+ {
+ component.DcPredictor = 0;
+ component.DcContext = 0;
+ component.DcStatistics?.Reset();
+ }
+
+ if (this.SpectralStart != 0)
+ {
+ component.AcStatistics?.Reset();
+ }
+ }
+
+ this.Reset();
+
+ if (this.scanComponentCount == 1)
+ {
+ this.ParseProgressiveDataNonInterleaved();
+ }
+ else
+ {
+ this.ParseProgressiveDataInterleaved();
+ }
+ }
+
+ private void CheckProgressiveData()
+ {
+ // Validate successive scan parameters.
+ // Logic has been adapted from libjpeg.
+ // See Table B.3 – Scan header parameter size and values. itu-t81.pdf
+ bool invalid = false;
+ if (this.SpectralStart == 0)
+ {
+ if (this.SpectralEnd != 0)
+ {
+ invalid = true;
+ }
+ }
+ else
+ {
+ // Need not check Ss/Se < 0 since they came from unsigned bytes.
+ if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63)
+ {
+ invalid = true;
+ }
+
+ // AC scans may have only one component.
+ if (this.scanComponentCount != 1)
+ {
+ invalid = true;
+ }
+ }
+
+ if (this.SuccessiveHigh != 0)
+ {
+ // Successive approximation refinement scan: must have Al = Ah-1.
+ if (this.SuccessiveHigh - 1 != this.SuccessiveLow)
+ {
+ invalid = true;
+ }
+ }
+
+ // TODO: How does this affect 12bit jpegs.
+ // According to libjpeg the range covers 8bit only?
+ if (this.SuccessiveLow > 13)
+ {
+ invalid = true;
+ }
+
+ if (invalid)
+ {
+ JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow);
+ }
+ }
+
+ private void ParseBaselineDataInterleaved()
+ {
+ int mcu = 0;
+ int mcusPerColumn = this.frame.McusPerColumn;
+ int mcusPerLine = this.frame.McusPerLine;
+ ref JpegBitReader reader = ref this.scanBuffer;
+
+ for (int j = 0; j < mcusPerColumn; j++)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+
+ // Decode from binary to spectral.
+ for (int i = 0; i < mcusPerLine; i++)
+ {
+ // Scan an interleaved mcu... process components in order.
+ int mcuCol = mcu % mcusPerLine;
+ for (int k = 0; k < this.scanComponentCount; k++)
+ {
+ int order = this.frame.ComponentOrder[k];
+ var component = this.components[order] as ArithmeticDecodingComponent;
+
+ ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId];
+ ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId];
+
+ int h = component.HorizontalSamplingFactor;
+ int v = component.VerticalSamplingFactor;
+
+ // Scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component.
+ int mcuColMulh = mcuCol * h;
+ for (int y = 0; y < v; y++)
+ {
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int x = 0; x < h; x++)
+ {
+ if (reader.NoData)
+ {
+ // It is very likely that some spectral data was decoded before we've encountered 'end of scan'
+ // so we need to decode what's left and return (or maybe throw?)
+ this.spectralConverter.ConvertStrideBaseline();
+ return;
+ }
+
+ int blockCol = mcuColMulh + x;
+
+ this.DecodeBlockBaseline(
+ component,
+ ref Unsafe.Add(ref blockRef, (nint)(uint)blockCol),
+ ref acDecodingTable,
+ ref dcDecodingTable);
+ }
+ }
+ }
+
+ // After all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval.
+ mcu++;
+ this.HandleRestart();
+ }
+
+ // Convert from spectral to actual pixels via given converter.
+ this.spectralConverter.ConvertStrideBaseline();
+ }
+ }
+
+ private void ParseBaselineDataSingleComponent()
+ {
+ var component = this.frame.Components[0] as ArithmeticDecodingComponent;
+ int mcuLines = this.frame.McusPerColumn;
+ int w = component.WidthInBlocks;
+ int h = component.SamplingFactors.Height;
+ ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId];
+ ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId];
+
+ ref JpegBitReader reader = ref this.scanBuffer;
+
+ for (int i = 0; i < mcuLines; i++)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+
+ // Decode from binary to spectral.
+ for (int j = 0; j < h; j++)
+ {
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int k = 0; k < w; k++)
+ {
+ if (reader.NoData)
+ {
+ // It is very likely that some spectral data was decoded before we've encountered 'end of scan'
+ // so we need to decode what's left and return (or maybe throw?)
+ this.spectralConverter.ConvertStrideBaseline();
+ return;
+ }
+
+ this.DecodeBlockBaseline(
+ component,
+ ref Unsafe.Add(ref blockRef, (nint)(uint)k),
+ ref acDecodingTable,
+ ref dcDecodingTable);
+
+ this.HandleRestart();
+ }
+ }
+
+ // Convert from spectral to actual pixels via given converter.
+ this.spectralConverter.ConvertStrideBaseline();
+ }
+ }
+
+ private void ParseBaselineDataNonInterleaved()
+ {
+ var component = (ArithmeticDecodingComponent)this.components[this.frame.ComponentOrder[0]];
+ ref JpegBitReader reader = ref this.scanBuffer;
+
+ int w = component.WidthInBlocks;
+ int h = component.HeightInBlocks;
+
+ ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId];
+ ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId];
+
+ for (int j = 0; j < h; j++)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
+ {
+ if (reader.NoData)
+ {
+ return;
+ }
+
+ this.DecodeBlockBaseline(
+ component,
+ ref Unsafe.Add(ref blockRef, (nint)(uint)i),
+ ref acDecodingTable,
+ ref dcDecodingTable);
+
+ this.HandleRestart();
+ }
+ }
+ }
+
+ private void ParseProgressiveDataInterleaved()
+ {
+ int mcu = 0;
+ int mcusPerColumn = this.frame.McusPerColumn;
+ int mcusPerLine = this.frame.McusPerLine;
+ ref JpegBitReader reader = ref this.scanBuffer;
+
+ for (int j = 0; j < mcusPerColumn; j++)
+ {
+ for (int i = 0; i < mcusPerLine; i++)
+ {
+ // Scan an interleaved mcu... process components in order.
+ int mcuRow = Math.DivRem(mcu, mcusPerLine, out int mcuCol);
+ for (int k = 0; k < this.scanComponentCount; k++)
+ {
+ int order = this.frame.ComponentOrder[k];
+ var component = this.components[order] as ArithmeticDecodingComponent;
+ ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId];
+
+ int h = component.HorizontalSamplingFactor;
+ int v = component.VerticalSamplingFactor;
+
+ // Scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component.
+ int mcuColMulh = mcuCol * h;
+ for (int y = 0; y < v; y++)
+ {
+ int blockRow = (mcuRow * v) + y;
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int x = 0; x < h; x++)
+ {
+ if (reader.NoData)
+ {
+ return;
+ }
+
+ int blockCol = mcuColMulh + x;
+
+ this.DecodeBlockProgressiveDc(
+ component,
+ ref Unsafe.Add(ref blockRef, (nint)(uint)blockCol),
+ ref dcDecodingTable);
+ }
+ }
+ }
+
+ // After all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval.
+ mcu++;
+ this.HandleRestart();
+ }
+ }
+ }
+
+ private void ParseProgressiveDataNonInterleaved()
+ {
+ var component = this.components[this.frame.ComponentOrder[0]] as ArithmeticDecodingComponent;
+ ref JpegBitReader reader = ref this.scanBuffer;
+
+ int w = component.WidthInBlocks;
+ int h = component.HeightInBlocks;
+
+ if (this.SpectralStart == 0)
+ {
+ ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId];
+
+ for (int j = 0; j < h; j++)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
+ {
+ if (reader.NoData)
+ {
+ return;
+ }
+
+ this.DecodeBlockProgressiveDc(
+ component,
+ ref Unsafe.Add(ref blockRef, (nint)(uint)i),
+ ref dcDecodingTable);
+
+ this.HandleRestart();
+ }
+ }
+ }
+ else
+ {
+ ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId];
+
+ for (int j = 0; j < h; j++)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
+ {
+ if (reader.NoData)
+ {
+ return;
+ }
+
+ this.DecodeBlockProgressiveAc(
+ component,
+ ref Unsafe.Add(ref blockRef, (nint)(uint)i),
+ ref acDecodingTable);
+
+ this.HandleRestart();
+ }
+ }
+ }
+ }
+
+ private void DecodeBlockProgressiveDc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable dcTable)
+ {
+ if (dcTable == null)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing");
+ }
+
+ ref JpegBitReader reader = ref this.scanBuffer;
+ ref short blockDataRef = ref Unsafe.As(ref block);
+
+ if (this.SuccessiveHigh == 0)
+ {
+ // First scan
+ // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients.
+
+ // Table F.4: Point to statistics bin S0 for DC coefficient coding.
+ ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), component.DcContext);
+
+ // Figure F.19: Decode_DC_DIFF
+ if (this.DecodeBinaryDecision(ref reader, ref st) == 0)
+ {
+ component.DcContext = 0;
+ }
+ else
+ {
+ // Figure F.21: Decoding nonzero value v.
+ // Figure F.22: Decoding the sign of v.
+ int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1));
+ st = ref Unsafe.Add(ref st, (nint)(uint)(2 + sign));
+
+ // Figure F.23: Decoding the magnitude category of v.
+ int m = this.DecodeBinaryDecision(ref reader, ref st);
+ if (m != 0)
+ {
+ st = ref component.DcStatistics.GetReference(20);
+ while (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ if ((m <<= 1) == 0x8000)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+
+ st = ref Unsafe.Add(ref st, 1);
+ }
+ }
+
+ // Section F.1.4.4.1.2: Establish dc_context conditioning category.
+ if (m < (int)((1L << dcTable.DcL) >> 1))
+ {
+ component.DcContext = 0; // Zero diff category.
+ }
+ else if (m > (int)((1L << dcTable.DcU) >> 1))
+ {
+ component.DcContext = 12 + (sign * 4); // Large diff category.
+ }
+ else
+ {
+ component.DcContext = 4 + (sign * 4); // Small diff category.
+ }
+
+ int v = m;
+
+ // Figure F.24: Decoding the magnitude bit pattern of v.
+ st = ref Unsafe.Add(ref st, 14);
+ while ((m >>= 1) != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ v |= m;
+ }
+ }
+
+ v += 1;
+ if (sign != 0)
+ {
+ v = -v;
+ }
+
+ component.DcPredictor = (short)(component.DcPredictor + v);
+ }
+
+ blockDataRef = (short)(component.DcPredictor << this.SuccessiveLow);
+ }
+ else
+ {
+ // Refinement scan.
+ ref byte st = ref this.GetFixedBinReference();
+
+ blockDataRef |= (short)(this.DecodeBinaryDecision(ref reader, ref st) << this.SuccessiveLow);
+ }
+ }
+
+ private void DecodeBlockProgressiveAc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable acTable)
+ {
+ ref JpegBitReader reader = ref this.scanBuffer;
+ ref short blockDataRef = ref Unsafe.As(ref block);
+
+ ArithmeticStatistics acStatistics = component.AcStatistics;
+ if (acStatistics == null || acTable == null)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing");
+ }
+
+ if (this.SuccessiveHigh == 0)
+ {
+ // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients.
+
+ // Figure F.20: Decode_AC_coefficients.
+ int start = this.SpectralStart;
+ int end = this.SpectralEnd;
+ int low = this.SuccessiveLow;
+
+ for (int k = start; k <= end; k++)
+ {
+ ref byte st = ref acStatistics.GetReference(3 * (k - 1));
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ break;
+ }
+
+ while (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) == 0)
+ {
+ st = ref Unsafe.Add(ref st, 3);
+ k++;
+ if (k > 63)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+ }
+
+ // Figure F.21: Decoding nonzero value v.
+ // Figure F.22: Decoding the sign of v.
+ int sign = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference());
+ st = ref Unsafe.Add(ref st, 2);
+
+ // Figure F.23: Decoding the magnitude category of v.
+ int m = this.DecodeBinaryDecision(ref reader, ref st);
+ if (m != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ m <<= 1;
+ st = ref acStatistics.GetReference(k <= acTable.AcKx ? 189 : 217);
+ while (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ if ((m <<= 1) == 0x8000)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+
+ st = ref Unsafe.Add(ref st, 1);
+ }
+ }
+ }
+
+ int v = m;
+
+ // Figure F.24: Decoding the magnitude bit pattern of v.
+ st = ref Unsafe.Add(ref st, 14);
+ while ((m >>= 1) != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ v |= m;
+ }
+ }
+
+ v += 1;
+ if (sign != 0)
+ {
+ v = -v;
+ }
+
+ Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)(v << low);
+ }
+ }
+ else
+ {
+ // Refinement scan.
+ this.ReadBlockProgressiveAcRefined(acStatistics, ref blockDataRef);
+ }
+ }
+
+ private void ReadBlockProgressiveAcRefined(ArithmeticStatistics acStatistics, ref short blockDataRef)
+ {
+ ref JpegBitReader reader = ref this.scanBuffer;
+ int start = this.SpectralStart;
+ int end = this.SpectralEnd;
+
+ int p1 = 1 << this.SuccessiveLow;
+ int m1 = -1 << this.SuccessiveLow;
+
+ // Establish EOBx (previous stage end-of-block) index.
+ int kex = end;
+ for (; kex > 0; kex--)
+ {
+ if (Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[kex]) != 0)
+ {
+ break;
+ }
+ }
+
+ for (int k = start; k <= end; k++)
+ {
+ ref byte st = ref acStatistics.GetReference(3 * (k - 1));
+ if (k > kex)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ break;
+ }
+ }
+
+ while (true)
+ {
+ ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]);
+ if (coef != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 2)) != 0)
+ {
+ coef = (short)(coef + (coef < 0 ? m1 : p1));
+ }
+
+ break;
+ }
+
+ if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) != 0)
+ {
+ bool flag = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()) != 0;
+ coef = (short)(coef + (flag ? m1 : p1));
+
+ break;
+ }
+
+ st = ref Unsafe.Add(ref st, 3);
+ k++;
+ if (k > end)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+ }
+ }
+ }
+
+ private void DecodeBlockBaseline(
+ ArithmeticDecodingComponent component,
+ ref Block8x8 destinationBlock,
+ ref ArithmeticDecodingTable acTable,
+ ref ArithmeticDecodingTable dcTable)
+ {
+ if (acTable is null)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing.");
+ }
+
+ if (dcTable is null)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing.");
+ }
+
+ ref JpegBitReader reader = ref this.scanBuffer;
+ ref short destinationRef = ref Unsafe.As(ref destinationBlock);
+
+ // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients.
+
+ // Table F.4: Point to statistics bin S0 for DC coefficient coding.
+ ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), component.DcContext);
+
+ /* Figure F.19: Decode_DC_DIFF */
+ if (this.DecodeBinaryDecision(ref reader, ref st) == 0)
+ {
+ component.DcContext = 0;
+ }
+ else
+ {
+ // Figure F.21: Decoding nonzero value v
+ // Figure F.22: Decoding the sign of v
+ int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1));
+ st = ref Unsafe.Add(ref st, (nint)(uint)(2 + sign));
+
+ // Figure F.23: Decoding the magnitude category of v.
+ int m = this.DecodeBinaryDecision(ref reader, ref st);
+ if (m != 0)
+ {
+ // Table F.4: X1 = 20
+ st = ref component.DcStatistics.GetReference(20);
+ while (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ if ((m <<= 1) == 0x8000)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+
+ st = ref Unsafe.Add(ref st, 1);
+ }
+ }
+
+ // Section F.1.4.4.1.2: Establish dc_context conditioning category.
+ if (m < (int)((1L << dcTable.DcL) >> 1))
+ {
+ component.DcContext = 0; // zero diff category
+ }
+ else if (m > (int)((1L << dcTable.DcU) >> 1))
+ {
+ component.DcContext = 12 + (sign * 4); // large diff category
+ }
+ else
+ {
+ component.DcContext = 4 + (sign * 4); // small diff category
+ }
+
+ int v = m;
+
+ // Figure F.24: Decoding the magnitude bit pattern of v.
+ st = ref Unsafe.Add(ref st, 14);
+ while ((m >>= 1) != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ v |= m;
+ }
+ }
+
+ v += 1;
+ if (sign != 0)
+ {
+ v = -v;
+ }
+
+ component.DcPredictor = (short)(component.DcPredictor + v);
+ }
+
+ destinationRef = (short)component.DcPredictor;
+
+ // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients.
+ ArithmeticStatistics acStatistics = component.AcStatistics;
+
+ for (int k = 1; k <= 63; k++)
+ {
+ st = ref acStatistics.GetReference(3 * (k - 1));
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ // EOB flag.
+ break;
+ }
+
+ while (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) == 0)
+ {
+ st = ref Unsafe.Add(ref st, 3);
+ k++;
+ if (k > 63)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+ }
+
+ // Figure F.21: Decoding nonzero value v.
+ // Figure F.22: Decoding the sign of v.
+ int sign = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference());
+ st = ref Unsafe.Add(ref st, 2);
+
+ // Figure F.23: Decoding the magnitude category of v.
+ int m = this.DecodeBinaryDecision(ref reader, ref st);
+ if (m != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ m <<= 1;
+ st = ref acStatistics.GetReference(k <= acTable.AcKx ? 189 : 217);
+ while (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ if ((m <<= 1) == 0x8000)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code.");
+ }
+
+ st = ref Unsafe.Add(ref st, 1);
+ }
+ }
+ }
+
+ int v = m;
+
+ // Figure F.24: Decoding the magnitude bit pattern of v.
+ st = ref Unsafe.Add(ref st, 14);
+ while ((m >>= 1) != 0)
+ {
+ if (this.DecodeBinaryDecision(ref reader, ref st) != 0)
+ {
+ v |= m;
+ }
+ }
+
+ v += 1;
+ if (sign != 0)
+ {
+ v = -v;
+ }
+
+ Unsafe.Add(ref destinationRef, ZigZag.TransposingOrder[k]) = (short)v;
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private bool HandleRestart()
+ {
+ if (this.restartInterval > 0 && (--this.todo) == 0)
+ {
+ if (this.scanBuffer.Marker == JpegConstants.Markers.XFF)
+ {
+ if (!this.scanBuffer.FindNextMarker())
+ {
+ return false;
+ }
+ }
+
+ this.todo = this.restartInterval;
+
+ foreach (ArithmeticDecodingComponent component in this.components)
+ {
+ component.DcPredictor = 0;
+ component.DcContext = 0;
+ component.DcStatistics?.Reset();
+ component.AcStatistics?.Reset();
+ }
+
+ this.Reset();
+
+ if (this.scanBuffer.HasRestartMarker())
+ {
+ this.Reset();
+ return true;
+ }
+
+ if (this.scanBuffer.HasBadMarker())
+ {
+ this.stream.Position = this.scanBuffer.MarkerPosition;
+ this.Reset();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void Reset()
+ {
+ for (int i = 0; i < this.components.Length; i++)
+ {
+ var component = this.components[i] as ArithmeticDecodingComponent;
+ component.DcPredictor = 0;
+ }
+
+ this.c = 0;
+ this.a = 0;
+ this.ct = -16; // Force reading 2 initial bytes to fill C.
+
+ this.scanBuffer.Reset();
+ }
+
+ private int DecodeBinaryDecision(ref JpegBitReader reader, ref byte st)
+ {
+ // Renormalization & data input per section D.2.6
+ while (this.a < 0x8000)
+ {
+ if (--this.ct < 0)
+ {
+ // Need to fetch next data byte.
+ reader.CheckBits();
+ int data = reader.GetBits(8);
+
+ // Insert data into C register.
+ this.c = (this.c << 8) | data;
+
+ // Update bit shift counter.
+ if ((this.ct += 8) < 0)
+ {
+ // Need more initial bytes.
+ if (++this.ct == 0)
+ {
+ // Got 2 initial bytes -> re-init A and exit loop
+ this.a = 0x8000; // e->a = 0x10000L after loop exit
+ }
+ }
+ }
+
+ this.a <<= 1;
+ }
+
+ // Fetch values from our compact representation of Table D.3(D.2):
+ // Qe values and probability estimation state machine
+ int sv = st;
+ int qe = ArithmeticTable[sv & 0x7f];
+ byte nl = (byte)qe;
+ qe >>= 8; // Next_Index_LPS + Switch_MPS
+ byte nm = (byte)qe;
+ qe >>= 8; // Next_Index_MPS
+
+ // Decode & estimation procedures per sections D.2.4 & D.2.5
+ int temp = this.a - qe;
+ this.a = temp;
+ temp <<= this.ct;
+ if (this.c >= temp)
+ {
+ this.c -= temp;
+
+ // Conditional LPS (less probable symbol) exchange
+ if (this.a < qe)
+ {
+ this.a = qe;
+ st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS
+ }
+ else
+ {
+ this.a = qe;
+ st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS
+ sv ^= 0x80; // Exchange LPS/MPS
+ }
+ }
+ else if (this.a < 0x8000)
+ {
+ // Conditional MPS (more probable symbol) exchange
+ if (this.a < qe)
+ {
+ st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS
+ sv ^= 0x80; // Exchange LPS/MPS
+ }
+ else
+ {
+ st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS
+ }
+ }
+
+ return sv >> 7;
+ }
+
+ // The following function specifies the packing of the four components
+ // into the compact INT32 representation.
+ // Note that this formula must match the actual arithmetic encoder and decoder implementation. The implementation has to be changed
+ // if this formula is changed.
+ // The current organization is leaned on Markus Kuhn's JBIG implementation (jbig_tab.c).
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int Pack(int a, int b, int c, int d)
+ => (a << 16) | (c << 8) | (d << 7) | b;
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs
new file mode 100644
index 000000000..c84831b3a
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal class ArithmeticStatistics
+ {
+ private readonly byte[] statistics;
+
+ public ArithmeticStatistics(bool dc, int identifier)
+ {
+ this.IsDcStatistics = dc;
+ this.Identifier = identifier;
+ this.statistics = dc ? new byte[64] : new byte[256];
+ }
+
+ public bool IsDcStatistics { get; private set; }
+
+ public int Identifier { get; private set; }
+
+ public ref byte GetReference() => ref this.statistics[0];
+
+ public ref byte GetReference(int offset) => ref this.statistics[offset];
+
+ public void Reset() => this.statistics.AsSpan().Clear();
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
index 2ae3ae86b..da2d5da65 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
@@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// Originally ported from
/// with additional fixes for both performance and common encoding errors.
///
- internal class HuffmanScanDecoder
+ internal class HuffmanScanDecoder : IJpegScanDecoder
{
private readonly BufferedReadStream stream;
@@ -26,12 +26,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Shortcut for .Components.
///
- private JpegComponent[] components;
+ private IJpegComponent[] components;
///
/// Number of component in the current scan.
///
- private int componentsCount;
+ private int scanComponentCount;
///
/// The reset interval determined by RST markers.
@@ -54,11 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private readonly HuffmanTable[] dcHuffmanTables;
///
- /// The AC Huffman tables
+ /// The AC Huffman tables.
///
private readonly HuffmanTable[] acHuffmanTables;
- private HuffmanScanBuffer scanBuffer;
+ private JpegBitReader scanBuffer;
private readonly SpectralConverter spectralConverter;
@@ -112,13 +112,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Decodes the entropy coded data.
///
- public void ParseEntropyCodedData(int componentCount)
+ /// Component count in the current scan.
+ public void ParseEntropyCodedData(int scanComponentCount)
{
this.cancellationToken.ThrowIfCancellationRequested();
- this.componentsCount = componentCount;
+ this.scanComponentCount = scanComponentCount;
- this.scanBuffer = new HuffmanScanBuffer(this.stream);
+ this.scanBuffer = new JpegBitReader(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
this.frame.AllocateComponents(fullScan);
@@ -138,6 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
+ ///
public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
this.frame = frame;
@@ -148,11 +150,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineData()
{
- if (this.componentsCount == this.frame.ComponentCount)
+ if (this.scanComponentCount != 1)
{
this.ParseBaselineDataInterleaved();
this.spectralConverter.CommitConversion();
}
+ else if (this.frame.ComponentCount == 1)
+ {
+ this.ParseBaselineDataSingleComponent();
+ this.spectralConverter.CommitConversion();
+ }
else
{
this.ParseBaselineDataNonInterleaved();
@@ -161,11 +168,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineDataInterleaved()
{
- // Interleaved
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine;
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref JpegBitReader buffer = ref this.scanBuffer;
for (int j = 0; j < mcusPerColumn; j++)
{
@@ -176,13 +182,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
// Scan an interleaved mcu... process components in order
int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < this.componentsCount; k++)
+ for (int k = 0; k < this.scanComponentCount; k++)
{
int order = this.frame.ComponentOrder[k];
- JpegComponent component = this.components[order];
+ var component = this.components[order] as JpegComponent;
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@@ -198,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
if (buffer.NoData)
{
- // It is very likely that some spectral data was decoded before we encountered EOI marker
+ // It is very likely that some spectral data was decoded before we've encountered 'end of scan'
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
return;
@@ -221,21 +227,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.HandleRestart();
}
- // convert from spectral to actual pixels via given converter
+ // Convert from spectral to actual pixels via given converter
this.spectralConverter.ConvertStrideBaseline();
}
}
private void ParseBaselineDataNonInterleaved()
{
- JpegComponent component = this.components[this.frame.ComponentOrder[0]];
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
+ ref JpegBitReader buffer = ref this.scanBuffer;
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int j = 0; j < h; j++)
{
@@ -261,6 +267,52 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
+ private void ParseBaselineDataSingleComponent()
+ {
+ var component = this.frame.Components[0] as JpegComponent;
+ int mcuLines = this.frame.McusPerColumn;
+ int w = component.WidthInBlocks;
+ int h = component.SamplingFactors.Height;
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
+
+ ref JpegBitReader buffer = ref this.scanBuffer;
+
+ for (int i = 0; i < mcuLines; i++)
+ {
+ this.cancellationToken.ThrowIfCancellationRequested();
+
+ // decode from binary to spectral
+ for (int j = 0; j < h; j++)
+ {
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int k = 0; k < w; k++)
+ {
+ if (buffer.NoData)
+ {
+ // It is very likely that some spectral data was decoded before we've encountered 'end of scan'
+ // so we need to decode what's left and return (or maybe throw?)
+ this.spectralConverter.ConvertStrideBaseline();
+ return;
+ }
+
+ this.DecodeBlockBaseline(
+ component,
+ ref Unsafe.Add(ref blockRef, k),
+ ref dcHuffmanTable,
+ ref acHuffmanTable);
+
+ this.HandleRestart();
+ }
+ }
+
+ // Convert from spectral to actual pixels via given converter
+ this.spectralConverter.ConvertStrideBaseline();
+ }
+ }
+
private void CheckProgressiveData()
{
// Validate successive scan parameters.
@@ -283,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
// AC scans may have only one component.
- if (this.componentsCount != 1)
+ if (this.scanComponentCount != 1)
{
invalid = true;
}
@@ -315,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.CheckProgressiveData();
- if (this.componentsCount == 1)
+ if (this.scanComponentCount == 1)
{
this.ParseProgressiveDataNonInterleaved();
}
@@ -331,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine;
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref JpegBitReader buffer = ref this.scanBuffer;
for (int j = 0; j < mcusPerColumn; j++)
{
@@ -340,11 +392,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < this.componentsCount; k++)
+ for (int k = 0; k < this.scanComponentCount; k++)
{
int order = this.frame.ComponentOrder[k];
- JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ var component = this.components[order] as JpegComponent;
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@@ -384,15 +436,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseProgressiveDataNonInterleaved()
{
- JpegComponent component = this.components[this.frame.ComponentOrder[0]];
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
+ ref JpegBitReader buffer = ref this.scanBuffer;
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
if (this.SpectralStart == 0)
{
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
for (int j = 0; j < h; j++)
{
@@ -419,7 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
else
{
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int j = 0; j < h; j++)
{
@@ -452,7 +504,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanTable acTable)
{
ref short blockDataRef = ref Unsafe.As(ref block);
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref JpegBitReader buffer = ref this.scanBuffer;
// DC
int t = buffer.DecodeHuffman(ref dcTable);
@@ -494,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable)
{
ref short blockDataRef = ref Unsafe.As(ref block);
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref JpegBitReader buffer = ref this.scanBuffer;
if (this.SuccessiveHigh == 0)
{
@@ -530,7 +582,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return;
}
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref JpegBitReader buffer = ref this.scanBuffer;
int start = this.SpectralStart;
int end = this.SpectralEnd;
int low = this.SuccessiveLow;
@@ -575,7 +627,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable)
{
// Refinement scan for these AC coefficients
- ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref JpegBitReader buffer = ref this.scanBuffer;
int start = this.SpectralStart;
int end = this.SpectralEnd;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
index bee5e0229..79713388b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
@@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to
- /// ensure terminates.
+ /// ensure terminates.
///
public fixed ulong MaxCode[18];
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs
index 54077339d..adab8c2ec 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs
@@ -10,6 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
internal interface IJpegComponent
{
+ ///
+ /// Gets the component id.
+ ///
+ byte Id { get; }
+
///
/// Gets the component's position in the components array.
///
@@ -25,6 +30,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
Size SamplingFactors { get; }
+ ///
+ /// Gets the horizontal sampling factor.
+ ///
+ int HorizontalSamplingFactor { get; }
+
+ ///
+ /// Gets the vertical sampling factor.
+ ///
+ int VerticalSamplingFactor { get; }
+
///
/// Gets the divisors needed to apply when calculating colors.
///
@@ -44,5 +59,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// We need to apply IDCT and dequantization to transform them into color-space blocks.
///
Buffer2D SpectralBlocks { get; }
+
+ ///
+ /// Gets or sets DC coefficient predictor.
+ ///
+ int DcPredictor { get; set; }
+
+ ///
+ /// Gets or sets the index for the DC table.
+ ///
+ int DcTableId { get; set; }
+
+ ///
+ /// Gets or sets the index for the AC table.
+ ///
+ int AcTableId { get; set; }
+
+ ///
+ /// Initializes component for future buffers initialization.
+ ///
+ /// Maximal horizontal subsampling factor among all the components.
+ /// Maximal vertical subsampling factor among all the components.
+ void Init(int maxSubFactorH, int maxSubFactorV);
+
+ ///
+ /// Allocates the spectral blocks.
+ ///
+ /// if set to true, use the full height of a block, otherwise use the vertical sampling factor.
+ void AllocateSpectral(bool fullScan);
+
+ ///
+ /// Releases resources.
+ ///
+ void Dispose();
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs
new file mode 100644
index 000000000..73ca9f08b
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ ///
+ /// Interface for a JPEG scan decoder.
+ ///
+ internal interface IJpegScanDecoder
+ {
+ ///
+ /// Sets the reset interval.
+ ///
+ int ResetInterval { set; }
+
+ ///
+ /// Gets or sets the spectral selection start.
+ ///
+ int SpectralStart { get; set; }
+
+ ///
+ /// Gets or sets the spectral selection end.
+ ///
+ int SpectralEnd { get; set; }
+
+ ///
+ /// Gets or sets the successive approximation high bit end.
+ ///
+ int SuccessiveHigh { get; set; }
+
+ ///
+ /// Gets or sets the successive approximation low bit end.
+ ///
+ int SuccessiveLow { get; set; }
+
+ ///
+ /// Decodes the entropy coded data.
+ ///
+ /// Component count in the current scan.
+ void ParseEntropyCodedData(int scanComponentCount);
+
+ ///
+ /// Sets the JpegFrame and its components and injects the frame data into the spectral converter.
+ ///
+ /// The frame.
+ /// The raw JPEG data.
+ void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
index 33815e539..dd7ca4e7f 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Gets the components.
///
- IJpegComponent[] Components { get; }
+ JpegComponent[] Components { get; }
///
/// Gets the quantization tables, in natural order.
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
similarity index 98%
rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
index 3664cb4eb..84013319e 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Used to buffer and track the bits read from the Huffman entropy encoded data.
///
- internal struct HuffmanScanBuffer
+ internal struct JpegBitReader
{
private readonly BufferedReadStream stream;
@@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Whether there is no more good data to pull from the stream for the current mcu.
private bool badData;
- public HuffmanScanBuffer(BufferedReadStream stream)
+ public JpegBitReader(BufferedReadStream stream)
{
this.stream = stream;
this.data = 0ul;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
index 3804e1c6c..5b0e87785 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Represents a single frame component.
///
- internal sealed class JpegComponent : IDisposable, IJpegComponent
+ internal class JpegComponent : IDisposable, IJpegComponent
{
private readonly MemoryAllocator memoryAllocator;
@@ -78,12 +78,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Gets or sets the index for the DC Huffman table.
///
- public int DCHuffmanTableId { get; set; }
+ public int DcTableId { get; set; }
///
/// Gets or sets the index for the AC Huffman table.
///
- public int ACHuffmanTableId { get; set; }
+ public int AcTableId { get; set; }
public JpegFrame Frame { get; }
@@ -119,11 +119,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
+ ///
public void AllocateSpectral(bool fullScan)
{
if (this.SpectralBlocks != null)
{
- // this method will be called each scan marker so we need to allocate only once
+ // This method will be called each scan marker so we need to allocate only once.
return;
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
index c3bf1cbdd..6173c4fbf 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
@@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// To be "more accurate", we need to emulate this by rounding!
workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
- // Write to color buffer acording to sampling factors
+ // Write to color buffer according to sampling factors
int xColorBufferStart = xBlock * this.blockAreaSize.Width;
workspaceBlock.ScaledCopyTo(
ref colorBufferRow[xColorBufferStart],
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
index fc109be26..db1febd39 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
@@ -6,14 +6,14 @@ using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
///
- /// Represent a single jpeg frame
+ /// Represent a single jpeg frame.
///
internal sealed class JpegFrame : IDisposable
{
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
- this.Extended = sofMarker.Marker == JpegConstants.Markers.SOF1;
- this.Progressive = sofMarker.Marker == JpegConstants.Markers.SOF2;
+ this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
+ this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10;
this.Precision = precision;
this.MaxColorChannelValue = MathF.Pow(2, precision) - 1;
@@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Gets the pixel size of the image.
///
- public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
+ public Size PixelSize => new(this.PixelWidth, this.PixelHeight);
///
/// Gets the number of components within a frame.
@@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Gets the mcu size of the image.
///
- public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
+ public Size McuSize => new(this.McusPerLine, this.McusPerColumn);
///
/// Gets the color depth, in number of bits per pixel.
@@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int i = 0; i < this.ComponentCount; i++)
{
- JpegComponent component = this.Components[i];
+ IJpegComponent component = this.Components[i];
component.Init(maxSubFactorH, maxSubFactorV);
}
}
@@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
for (int i = 0; i < this.ComponentCount; i++)
{
- JpegComponent component = this.Components[i];
+ IJpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index 40411ef28..532892e06 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
@@ -95,7 +95,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
- return this.pixelBuffer;
+ var buffer = this.pixelBuffer;
+ this.pixelBuffer = null;
+ return buffer;
}
///
@@ -111,7 +113,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight;
// pixel buffer for resulting image
- this.pixelBuffer = allocator.Allocate2D(frame.PixelWidth, frame.PixelHeight);
+ this.pixelBuffer = allocator.Allocate2D(
+ frame.PixelWidth,
+ frame.PixelHeight,
+ this.configuration.PreferContiguousImageBuffers);
this.paddedProxyPixelRow = allocator.Allocate(frame.PixelWidth + 3);
// component processors from spectral to Rgba32
@@ -207,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
+ this.pixelBuffer?.Dispose();
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
index 6c402fcfd..e87f2fc57 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
@@ -2,8 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@@ -74,6 +79,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span);
+ if (RgbToYCbCrConverterVectorized.IsSupported)
+ {
+ ConvertAvx(ref l8Start, ref yBlock);
+ }
+ else
+ {
+ ConvertScalar(ref l8Start, ref yBlock);
+ }
+ }
+
+ ///
+ /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
+ ///
+ /// Start of span of L8 pixels with size of 64
+ /// 8x8 destination matrix of Luminance(Y) converted data
+ private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock)
+ {
+ Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ ref Vector128 l8ByteSpan = ref Unsafe.As>(ref l8Start);
+ ref Vector256 destRef = ref yBlock.V0;
+
+ const int bytesPerL8Stride = 8;
+ for (nint i = 0; i < 8; i++)
+ {
+ Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i)));
+ }
+#endif
+ }
+
+ ///
+ /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats.
+ ///
+ /// Start of span of L8 pixels with size of 64
+ /// 8x8 destination matrix of Luminance(Y) converted data
+ private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock)
+ {
for (int i = 0; i < Block8x8F.Size; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
index 789277d7d..e2d12916c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
@@ -2,8 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@@ -94,10 +99,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;
- CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
+ if (RgbToYCbCrConverterVectorized.IsSupported)
+ {
+ ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
+ }
+ else
+ {
+ ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
+ }
+ }
+
+ ///
+ /// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
+ ///
+ /// Span of Rgb24 pixels with size of 64
+ /// 8x8 destination matrix of Red converted data
+ /// 8x8 destination matrix of Blue converted data
+ /// 8x8 destination matrix of Green converted data
+ private static void ConvertAvx(Span rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock)
+ {
+ Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan));
+ ref Vector256 redRef = ref rBlock.V0;
+ ref Vector256 greenRef = ref gBlock.V0;
+ ref Vector256 blueRef = ref bBlock.V0;
+ var zero = Vector256.Create(0).AsByte();
+
+ var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes));
+ var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb));
+ Vector256 rgb, rg, bx;
+
+ const int bytesPerRgbStride = 24;
+ for (nint i = 0; i < 8; i++)
+ {
+ rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte();
+
+ rgb = Avx2.Shuffle(rgb, extractRgbMask);
+
+ rg = Avx2.UnpackLow(rgb, zero);
+ bx = Avx2.UnpackHigh(rgb, zero);
+
+ Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
+ Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
+ Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
+ }
+#endif
}
- private static void CopyToBlock(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
+ private static void ConvertScalar(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
index 9566ee862..d7542d7a5 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
@@ -60,13 +60,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
#if SUPPORTS_RUNTIME_INTRINSICS
- private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[]
+ internal static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[]
{
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
};
- private static ReadOnlySpan ExtractRgb => new byte[]
+ internal static ReadOnlySpan ExtractRgb => new byte[]
{
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF
diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
index 89c4de550..20edf4037 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
@@ -306,17 +306,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public const int RegisterSize = 64;
///
- /// The number of bits to fetch when filling the buffer.
+ /// The number of bits to fetch when filling the buffer.
///
public const int FetchBits = 48;
///
- /// The number of times to read the input stream when filling the buffer.
+ /// The number of times to read the input stream when filling the buffer.
///
public const int FetchLoop = FetchBits / 8;
///
- /// The minimum number of bits allowed before by the before fetching.
+ /// The minimum number of bits allowed before by the before fetching.
///
public const int MinBits = RegisterSize - FetchBits;
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index 18212ffc7..22a9801b8 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@@ -17,56 +16,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public bool IgnoreMetadata { get; set; }
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
- return decoder.Decode(configuration, stream);
+ return decoder.Decode(configuration, stream, cancellationToken);
}
///
- public Image Decode(Configuration configuration, Stream stream)
- => this.Decode(configuration, stream);
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => this.Decode(configuration, stream, cancellationToken);
///
- public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- Guard.NotNull(stream, nameof(stream));
-
- using var decoder = new JpegDecoderCore(configuration, this);
- return decoder.DecodeAsync(configuration, stream, cancellationToken);
- }
-
- ///
- public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- => await this.DecodeAsync(configuration, stream, cancellationToken)
- .ConfigureAwait(false);
-
- ///
- public IImageInfo Identify(Configuration configuration, Stream stream)
+ public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
- return decoder.Identify(configuration, stream);
- }
-
- ///
- public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- {
- Guard.NotNull(stream, nameof(stream));
-
- // The introduction of a local variable that refers to an object the implements
- // IDisposable means you must use async/await, where the compiler generates the
- // state machine and a continuation.
- using (var decoder = new JpegDecoderCore(configuration, this))
- {
- return await decoder.IdentifyAsync(configuration, stream, cancellationToken)
- .ConfigureAwait(false);
- }
+ return decoder.Identify(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 023928f37..a07db1c95 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
+using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -97,7 +98,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// Scan decoder.
///
- private HuffmanScanDecoder scanDecoder;
+ private IJpegScanDecoder scanDecoder;
+
+ ///
+ /// The arithmetic decoding tables.
+ ///
+ private List arithmeticDecodingTables;
+
+ ///
+ /// The restart interval.
+ ///
+ private int? resetInterval;
///
/// Initializes a new instance of the class.
@@ -140,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegComponent[] Components => this.Frame.Components;
///
- IJpegComponent[] IRawJpegData.Components => this.Components;
+ JpegComponent[] IRawJpegData.Components => this.Components;
///
public Block8x8F[] QuantizationTables { get; private set; }
@@ -188,9 +199,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
using var spectralConverter = new SpectralConverter(this.Configuration);
- var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
-
- this.ParseStream(stream, scanDecoder, cancellationToken);
+ this.ParseStream(stream, spectralConverter, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
@@ -206,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
- this.ParseStream(stream, scanDecoder: null, cancellationToken);
+ this.ParseStream(stream, spectralConverter: null, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
@@ -222,17 +231,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips).
///
/// The table bytes.
- /// The scan decoder.
- public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder)
+ /// The scan decoder.
+ public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder)
{
this.Metadata = new ImageMetadata();
this.QuantizationTables = new Block8x8F[4];
- this.scanDecoder = huffmanScanDecoder;
+ this.scanDecoder = scanDecoder;
+ if (tableBytes.Length < 4)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
+ }
+
using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms);
// Check for the Start Of Image marker.
- stream.Read(this.markerBuffer, 0, 2);
+ int bytesRead = stream.Read(this.markerBuffer, 0, 2);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
@@ -240,16 +254,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// Read next marker.
- stream.Read(this.markerBuffer, 0, 2);
- byte marker = this.markerBuffer[1];
- fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
+ bytesRead = stream.Read(this.markerBuffer, 0, 2);
+ fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
// Get the marker length.
- int remaining = this.ReadUint16(stream) - 2;
+ int markerContentByteSize = this.ReadUint16(stream) - 2;
+
+ // Check whether stream actually has enought bytes to read
+ // markerContentByteSize is always positive so we cast
+ // to uint to avoid sign extension
+ if (stream.RemainingBytes < (uint)markerContentByteSize)
+ {
+ JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
+ }
switch (fileMarker.Marker)
{
@@ -259,13 +280,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.RST7:
break;
case JpegConstants.Markers.DHT:
- this.ProcessDefineHuffmanTablesMarker(stream, remaining);
+ this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DQT:
- this.ProcessDefineQuantizationTablesMarker(stream, remaining);
+ this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DRI:
- this.ProcessDefineRestartIntervalMarker(stream, remaining);
+ this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.EOI:
return;
@@ -273,7 +294,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// Read next marker.
- stream.Read(this.markerBuffer, 0, 2);
+ bytesRead = stream.Read(this.markerBuffer, 0, 2);
+ if (bytesRead != 2)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
+ }
+
fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
}
}
@@ -282,13 +308,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Parses the input stream for file markers.
///
/// The input stream.
- /// Scan decoder used exclusively to decode SOS marker.
+ /// The spectral converter to use.
/// The token to monitor cancellation.
- internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken)
+ internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralConverter, CancellationToken cancellationToken)
{
- bool metadataOnly = scanDecoder == null;
+ bool metadataOnly = spectralConverter == null;
- this.scanDecoder = scanDecoder;
+ this.scanDecoder ??= new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
this.Metadata = new ImageMetadata();
@@ -315,14 +341,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid)
{
// Get the marker length.
- int remaining = this.ReadUint16(stream) - 2;
+ int markerContentByteSize = this.ReadUint16(stream) - 2;
+
+ // Check whether stream actually has enough bytes to read
+ // markerContentByteSize is always positive so we cast
+ // to uint to avoid sign extension.
+ if (stream.RemainingBytes < (uint)markerContentByteSize)
+ {
+ JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
+ }
switch (fileMarker.Marker)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
- this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly);
+
+ this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly);
+ break;
+
+ case JpegConstants.Markers.SOF9:
+ case JpegConstants.Markers.SOF10:
+ case JpegConstants.Markers.SOF13:
+ case JpegConstants.Markers.SOF14:
+ this.scanDecoder = new ArithmeticScanDecoder(stream, spectralConverter, cancellationToken);
+ if (this.resetInterval.HasValue)
+ {
+ this.scanDecoder.ResetInterval = this.resetInterval.Value;
+ }
+
+ this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly);
break;
case JpegConstants.Markers.SOF5:
@@ -338,19 +386,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported.");
break;
- case JpegConstants.Markers.SOF9:
- case JpegConstants.Markers.SOF10:
case JpegConstants.Markers.SOF11:
- case JpegConstants.Markers.SOF13:
- case JpegConstants.Markers.SOF14:
case JpegConstants.Markers.SOF15:
- JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported.");
+ JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with lossless arithmetic coding is not supported.");
break;
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
- this.ProcessStartOfScanMarker(stream, remaining);
+ this.ProcessStartOfScanMarker(stream, markerContentByteSize);
break;
}
else
@@ -364,41 +408,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (metadataOnly)
{
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
}
else
{
- this.ProcessDefineHuffmanTablesMarker(stream, remaining);
+ this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
}
break;
case JpegConstants.Markers.DQT:
- this.ProcessDefineQuantizationTablesMarker(stream, remaining);
+ this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
}
else
{
- this.ProcessDefineRestartIntervalMarker(stream, remaining);
+ this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
}
break;
case JpegConstants.Markers.APP0:
- this.ProcessApplicationHeaderMarker(stream, remaining);
+ this.ProcessApplicationHeaderMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP1:
- this.ProcessApp1Marker(stream, remaining);
+ this.ProcessApp1Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP2:
- this.ProcessApp2Marker(stream, remaining);
+ this.ProcessApp2Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP3:
@@ -411,24 +455,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.APP13:
- this.ProcessApp13Marker(stream, remaining);
+ this.ProcessApp13Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP14:
- this.ProcessApp14Marker(stream, remaining);
+ this.ProcessApp14Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM:
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.DAC:
- JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported.");
+ if (metadataOnly)
+ {
+ stream.Skip(markerContentByteSize);
+ }
+ else
+ {
+ this.ProcessArithmeticTable(stream, markerContentByteSize);
+ }
+
break;
}
}
@@ -677,7 +729,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- /// Processes the App1 marker retrieving any stored metadata
+ /// Processes the App1 marker retrieving any stored metadata.
///
/// The input stream.
/// The remaining bytes in the segment block.
@@ -687,7 +739,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int XmpMarkerLength = 29;
if (remaining < ExifMarkerLength || this.IgnoreMetadata)
{
- // Skip the application header length
+ // Skip the application header length.
stream.Skip(remaining);
return;
}
@@ -697,12 +749,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
}
- // XMP marker is the longest, so read at least that many bytes into temp.
+ // XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes.
stream.Read(this.temp, 0, ExifMarkerLength);
+ remaining -= ExifMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker))
{
- remaining -= ExifMarkerLength;
this.hasExif = true;
byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining);
@@ -713,7 +765,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
- // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers
+ // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers.
this.ExtendProfile(ref this.exifData, profile);
}
@@ -722,9 +774,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{
- stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength);
- remaining -= XmpMarkerLength;
- if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength)))
+ const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
+ if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata)
+ {
+ // Skip the application header length.
+ stream.Skip(remaining);
+ return;
+ }
+
+ stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes);
+ remaining -= remainingXmpMarkerBytes;
+ if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
{
this.hasXmp = true;
byte[] profile = new byte[remaining];
@@ -736,7 +796,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
- // If the XMP information exceeds 64K, it will be split over multiple APP1 markers
+ // If the XMP information exceeds 64K, it will be split over multiple APP1 markers.
this.ExtendProfile(ref this.xmpData, profile);
}
@@ -855,6 +915,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ ///
+ /// Processes a DAC marker, decoding the arithmetic tables.
+ ///
+ /// The input stream.
+ /// The remaining bytes in the segment block.
+ private void ProcessArithmeticTable(BufferedReadStream stream, int remaining)
+ {
+ this.arithmeticDecodingTables ??= new List(4);
+
+ while (remaining > 0)
+ {
+ int tableClassAndIdentifier = stream.ReadByte();
+ remaining--;
+ byte tableClass = (byte)(tableClassAndIdentifier >> 4);
+ byte identifier = (byte)(tableClassAndIdentifier & 0xF);
+
+ byte conditioningTableValue = (byte)stream.ReadByte();
+ remaining--;
+
+ var arithmeticTable = new ArithmeticDecodingTable(tableClass, identifier);
+ arithmeticTable.Configure(conditioningTableValue);
+
+ bool tableEntryReplaced = false;
+ for (int i = 0; i < this.arithmeticDecodingTables.Count; i++)
+ {
+ ArithmeticDecodingTable item = this.arithmeticDecodingTables[i];
+ if (item.TableClass == arithmeticTable.TableClass && item.Identifier == arithmeticTable.Identifier)
+ {
+ this.arithmeticDecodingTables[i] = arithmeticTable;
+ tableEntryReplaced = true;
+ break;
+ }
+ }
+
+ if (!tableEntryReplaced)
+ {
+ this.arithmeticDecodingTables.Add(arithmeticTable);
+ }
+ }
+ }
+
///
/// Reads the adobe image resource block name: a Pascal string (padded to make size even).
///
@@ -916,7 +1017,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The input stream.
/// The remaining bytes in the segment block.
///
- /// Thrown if the tables do not match the header
+ /// Thrown if the tables do not match the header.
///
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining)
{
@@ -1022,8 +1123,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The input stream.
/// The remaining bytes in the segment block.
/// The current frame marker.
- /// Whether to parse metadata only
- private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, bool metadataOnly)
+ /// The jpeg decoding component type.
+ /// Whether to parse metadata only.
+ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly)
{
if (this.Frame != null)
{
@@ -1035,17 +1137,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
}
- // Read initial marker definitions
+ // Read initial marker definitions.
const int length = 6;
- stream.Read(this.temp, 0, length);
+ int bytesRead = stream.Read(this.temp, 0, length);
+ if (bytesRead != length)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data.");
+ }
- // 1 byte: Bits/sample precision
+ // 1 byte: Bits/sample precision.
byte precision = this.temp[0];
- // Validate: only 8-bit and 12-bit precisions are supported
+ // Validate: only 8-bit and 12-bit precisions are supported.
if (Array.IndexOf(this.supportedPrecisions, precision) == -1)
{
- JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported.");
}
// 2 byte: Height
@@ -1054,18 +1160,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// 2 byte: Width
int frameWidth = (this.temp[3] << 8) | this.temp[4];
- // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
+ // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that).
if (frameHeight == 0 || frameWidth == 0)
{
JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight);
}
- // 1 byte: Number of components
+ // 1 byte: Number of components.
byte componentCount = this.temp[5];
// Validate: componentCount more than 4 can lead to a buffer overflow during stream
- // reading so we must limit it to 4
- // We do not support jpeg images with more than 4 components anyway
+ // reading so we must limit it to 4.
+ // We do not support jpeg images with more than 4 components anyway.
if (componentCount > 4)
{
JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount);
@@ -1134,9 +1240,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex);
}
- var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
+ IJpegComponent component = decodingComponentType is ComponentType.Huffman ?
+ new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) :
+ new ArithmeticDecodingComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
- this.Frame.Components[i] = component;
+ this.Frame.Components[i] = (JpegComponent)component;
this.Frame.ComponentIds[i] = componentId;
index += componentBytes;
@@ -1164,11 +1272,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int codeValuesMaxByteSize = 256;
const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize;
+ var huffmanScanDecoder = this.scanDecoder as HuffmanScanDecoder;
+ if (huffmanScanDecoder is null)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("missing huffman table data");
+ }
+
int length = remaining;
using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize))
{
Span bufferSpan = buffer.GetSpan();
- Span huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);
+ Span huffmanLengthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);
Span huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize);
Span tableWorkspace = MemoryMarshal.Cast(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize));
@@ -1190,12 +1304,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}.");
}
- stream.Read(huffmanLegthsSpan, 1, 16);
+ stream.Read(huffmanLengthsSpan, 1, 16);
int codeLengthSum = 0;
for (int j = 1; j < 17; j++)
{
- codeLengthSum += huffmanLegthsSpan[j];
+ codeLengthSum += huffmanLengthsSpan[j];
}
length -= 17;
@@ -1209,10 +1323,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
i += 17 + codeLengthSum;
- this.scanDecoder.BuildHuffmanTable(
+ huffmanScanDecoder!.BuildHuffmanTable(
tableType,
tableIndex,
- huffmanLegthsSpan,
+ huffmanLengthsSpan,
huffmanValuesSpan.Slice(0, codeLengthSum),
tableWorkspace);
}
@@ -1220,8 +1334,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
- /// macroblocks
+ /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers,
+ /// in macroblocks.
///
/// The input stream.
/// The remaining bytes in the segment block.
@@ -1232,7 +1346,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining);
}
- this.scanDecoder.ResetInterval = this.ReadUint16(stream);
+ // Save the reset interval, because it can come before or after the SOF marker.
+ // If the reset interval comes after the SOF marker, the scanDecoder has not been created.
+ this.resetInterval = this.ReadUint16(stream);
+
+ if (this.scanDecoder != null)
+ {
+ this.scanDecoder.ResetInterval = this.resetInterval.Value;
+ }
}
///
@@ -1245,7 +1366,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
}
- // 1 byte: Number of components in scan
+ // 1 byte: Number of components in scan.
int selectorsCount = stream.ReadByte();
// Validate: 0 < count <= totalComponents
@@ -1255,11 +1376,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}.");
}
- // Validate: marker must contain exactly (4 + selectorsCount*2) bytes
+ // Validate: Marker must contain exactly (4 + selectorsCount*2) bytes
int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes)
{
- JpegThrowHelper.ThrowBadMarker("SOS", remaining);
+ JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining);
}
// selectorsCount*2 bytes: component index + huffman tables indices
@@ -1282,7 +1403,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
- // Validate: must be found among registered components
+ // Validate: Must be found among registered components.
if (componentIndex == -1)
{
// TODO: extract as separate method?
@@ -1291,7 +1412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.Frame.ComponentOrder[i / 2] = (byte)componentIndex;
- JpegComponent component = this.Frame.Components[componentIndex];
+ IJpegComponent component = this.Frame.Components[componentIndex];
// 1 byte: Huffman table selectors.
// 4 bits - dc
@@ -1307,12 +1428,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}");
}
- component.DCHuffmanTableId = dcTableIndex;
- component.ACHuffmanTableId = acTableIndex;
+ component.DcTableId = dcTableIndex;
+ component.AcTableId = acTableIndex;
}
- // 3 bytes: Progressive scan decoding data
- stream.Read(this.temp, 0, 3);
+ // 3 bytes: Progressive scan decoding data.
+ int bytesRead = stream.Read(this.temp, 0, 3);
+ if (bytesRead != 3)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data");
+ }
int spectralStart = this.temp[0];
this.scanDecoder.SpectralStart = spectralStart;
@@ -1324,18 +1449,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4;
this.scanDecoder.SuccessiveLow = successiveApproximation & 15;
+ if (this.scanDecoder is ArithmeticScanDecoder arithmeticScanDecoder)
+ {
+ arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables);
+ }
+
this.scanDecoder.ParseEntropyCodedData(selectorsCount);
}
///
- /// Reads a from the stream advancing it by two bytes
+ /// Reads a from the stream advancing it by two bytes.
///
/// The input stream.
/// The
[MethodImpl(InliningOptions.ShortMethod)]
private ushort ReadUint16(BufferedReadStream stream)
{
- stream.Read(this.markerBuffer, 0, 2);
+ int bytesRead = stream.Read(this.markerBuffer, 0, 2);
+ if (bytesRead != 2)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort.");
+ }
+
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
index b238e45ef..1073ffff7 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
@@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker.");
+
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}.");
diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs
index 2eebbb1d9..97a9cb7d7 100644
--- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
@@ -30,50 +29,26 @@ namespace SixLabors.ImageSharp.Formats.Pbm
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
{
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
- return decoder.Decode(configuration, stream);
+ return decoder.Decode(configuration, stream, cancellationToken);
}
///
- public Image Decode(Configuration configuration, Stream stream)
- => this.Decode(configuration, stream);
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => this.Decode(configuration, stream, cancellationToken);
///
- public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- Guard.NotNull(stream, nameof(stream));
-
- var decoder = new PbmDecoderCore(configuration);
- return decoder.DecodeAsync(configuration, stream, cancellationToken);
- }
-
- ///
- public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- => await this.DecodeAsync(configuration, stream, cancellationToken)
- .ConfigureAwait(false);
-
- ///
- public IImageInfo Identify(Configuration configuration, Stream stream)
- {
- Guard.NotNull(stream, nameof(stream));
-
- var decoder = new PbmDecoderCore(configuration);
- return decoder.Identify(configuration, stream);
- }
-
- ///
- public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
- return decoder.IdentifyAsync(configuration, stream, cancellationToken);
+ return decoder.Identify(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
index 83c638934..44a16f154 100644
--- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
@@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
internal static class AverageFilter
{
///
- /// Decodes the scanline
+ /// Decodes a scanline, which was filtered with the average filter.
///
- /// The scanline to decode
+ /// The scanline to decode.
/// The previous scanline.
/// The bytes per pixel.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -30,11 +30,66 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
+ // The Avg filter predicts each pixel as the (truncated) average of a and b:
+ // Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
+ // With pixels positioned like this:
+ // prev: c b
+ // row: a d
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse2.IsSupported && bytesPerPixel is 4)
+ {
+ DecodeSse2(scanline, previousScanline);
+ }
+ else
+#endif
+ {
+ DecodeScalar(scanline, previousScanline, bytesPerPixel);
+ }
+ }
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void DecodeSse2(Span scanline, Span previousScanline)
+ {
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
- // Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
- int x = 1;
+ Vector128 d = Vector128.Zero;
+ var ones = Vector128.Create((byte)1);
+
+ int rb = scanline.Length;
+ nint offset = 1;
+ while (rb >= 4)
+ {
+ ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
+ Vector128 a = d;
+ Vector128 b = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte();
+ d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte();
+
+ // PNG requires a truncating average, so we can't just use _mm_avg_epu8,
+ // but we can fix it up by subtracting off 1 if it rounded up.
+ Vector128 avg = Sse2.Average(a, b);
+ Vector128 xor = Sse2.Xor(a, b);
+ Vector128 and = Sse2.And(xor, ones);
+ avg = Sse2.Subtract(avg, and);
+ d = Sse2.Add(d, avg);
+
+ // Store the result.
+ Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32());
+
+ rb -= 4;
+ offset += 4;
+ }
+ }
+#endif
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel)
+ {
+ ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
+ ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
+
+ nint x = 1;
for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x)
{
ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
@@ -52,13 +107,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
}
///
- /// Encodes the scanline
+ /// Encodes a scanline with the average filter applied.
///
- /// The scanline to encode
+ /// The scanline to encode.
/// The previous scanline.
/// The filtered scanline result.
/// The bytes per pixel.
- /// The sum of the total variance of the filtered row
+ /// The sum of the total variance of the filtered row.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum)
{
diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
index 6a89a1122..0553eb46a 100644
--- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
@@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
internal static class PaethFilter
{
///
- /// Decodes the scanline
+ /// Decodes a scanline, which was filtered with the paeth filter.
///
- /// The scanline to decode
+ /// The scanline to decode.
/// The previous scanline.
/// The bytes per pixel.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -32,6 +32,86 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
+ // Paeth tries to predict pixel d using the pixel to the left of it, a,
+ // and two pixels from the previous row, b and c:
+ // prev: c b
+ // row: a d
+ // The Paeth function predicts d to be whichever of a, b, or c is nearest to
+ // p = a + b - c.
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse41.IsSupported && bytesPerPixel is 4)
+ {
+ DecodeSse41(scanline, previousScanline);
+ }
+ else
+#endif
+ {
+ DecodeScalar(scanline, previousScanline, bytesPerPixel);
+ }
+ }
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void DecodeSse41(Span scanline, Span previousScanline)
+ {
+ ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
+ ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
+
+ Vector128 b = Vector128.Zero;
+ Vector128 d = Vector128.Zero;
+
+ int rb = scanline.Length;
+ nint offset = 1;
+ while (rb >= 4)
+ {
+ ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
+
+ // It's easiest to do this math (particularly, deal with pc) with 16-bit intermediates.
+ Vector128 c = b;
+ Vector128 a = d;
+ b = Sse2.UnpackLow(
+ Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(),
+ Vector128.Zero);
+ d = Sse2.UnpackLow(
+ Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(),
+ Vector128.Zero);
+
+ // (p-a) == (a+b-c - a) == (b-c)
+ Vector128 pa = Sse2.Subtract(b.AsInt16(), c.AsInt16());
+
+ // (p-b) == (a+b-c - b) == (a-c)
+ Vector128 pb = Sse2.Subtract(a.AsInt16(), c.AsInt16());
+
+ // (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c)
+ Vector128 pc = Sse2.Add(pa.AsInt16(), pb.AsInt16());
+
+ pa = Ssse3.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */
+ pb = Ssse3.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */
+ pc = Ssse3.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */
+
+ Vector128 smallest = Sse2.Min(pc, Sse2.Min(pa, pb));
+
+ // Paeth breaks ties favoring a over b over c.
+ Vector128 mask = Sse41.BlendVariable(c, b, Sse2.CompareEqual(smallest, pb).AsByte());
+ Vector128 nearest = Sse41.BlendVariable(mask, a, Sse2.CompareEqual(smallest, pa).AsByte());
+
+ // Note `_epi8`: we need addition to wrap modulo 255.
+ d = Sse2.Add(d, nearest);
+
+ // Store the result.
+ Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(Sse2.PackUnsignedSaturate(d.AsInt16(), d.AsInt16()).AsInt32());
+
+ rb -= 4;
+ offset += 4;
+ }
+ }
+
+#endif
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel)
+ {
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
@@ -56,13 +136,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
}
///
- /// Encodes the scanline
+ /// Encodes a scanline and applies the paeth filter.
///
/// The scanline to encode
/// The previous scanline.
/// The filtered scanline result.
/// The bytes per pixel.
- /// The sum of the total variance of the filtered row
+ /// The sum of the total variance of the filtered row.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum)
{
diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
index c28b877e4..eaa4dc034 100644
--- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
@@ -21,17 +21,57 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
internal static class SubFilter
{
///
- /// Decodes the scanline
+ /// Decodes a scanline, which was filtered with the sub filter.
///
- /// The scanline to decode
+ /// The scanline to decode.
/// The bytes per pixel.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span scanline, int bytesPerPixel)
+ {
+ // The Sub filter predicts each pixel as the previous pixel.
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse2.IsSupported && bytesPerPixel is 4)
+ {
+ DecodeSse2(scanline);
+ }
+ else
+#endif
+ {
+ DecodeScalar(scanline, bytesPerPixel);
+ }
+ }
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ private static void DecodeSse2(Span scanline)
+ {
+ ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
+
+ Vector128 d = Vector128.Zero;
+
+ int rb = scanline.Length;
+ nint offset = 1;
+ while (rb >= 4)
+ {
+ ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
+ Vector128 a = d;
+ d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte();
+
+ d = Sse2.Add(d, a);
+
+ Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32());
+
+ rb -= 4;
+ offset += 4;
+ }
+ }
+#endif
+
+ private static void DecodeScalar(Span scanline, int bytesPerPixel)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
// Sub(x) + Raw(x-bpp)
- int x = bytesPerPixel + 1;
+ nint x = bytesPerPixel + 1;
Unsafe.Add(ref scanBaseRef, x);
for (; x < scanline.Length; ++x)
{
@@ -42,12 +82,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
}
///
- /// Encodes the scanline
+ /// Encodes a scanline with the sup filter applied.
///
- /// The scanline to encode
+ /// The scanline to encode.
/// The filtered scanline result.
/// The bytes per pixel.
- /// The sum of the total variance of the filtered row
+ /// The sum of the total variance of the filtered row.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum)
{
diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
index 7e0286991..0d24d9c5d 100644
--- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
@@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
internal static class UpFilter
{
///
- /// Decodes the scanline
+ /// Decodes a scanline, which was filtered with the up filter.
///
/// The scanline to decode
/// The previous scanline.
@@ -30,6 +30,91 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported)
+ {
+ DecodeAvx2(scanline, previousScanline);
+ }
+ else if (Sse2.IsSupported)
+ {
+ DecodeSse2(scanline, previousScanline);
+ }
+ else
+#endif
+ {
+ DecodeScalar(scanline, previousScanline);
+ }
+ }
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ private static void DecodeAvx2(Span scanline, Span previousScanline)
+ {
+ ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
+ ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
+
+ // Up(x) + Prior(x)
+ int rb = scanline.Length;
+ nint offset = 1;
+ const int bytesPerBatch = 32;
+ while (rb >= bytesPerBatch)
+ {
+ ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
+ Vector256 current = Unsafe.As>(ref scanRef);
+ Vector256 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset));
+
+ Vector256 sum = Avx2.Add(up, current);
+ Unsafe.As>(ref scanRef) = sum;
+
+ offset += bytesPerBatch;
+ rb -= bytesPerBatch;
+ }
+
+ // Handle left over.
+ for (nint i = offset; i < scanline.Length; i++)
+ {
+ ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset);
+ byte above = Unsafe.Add(ref prevBaseRef, offset);
+ scan = (byte)(scan + above);
+ offset++;
+ }
+ }
+
+ private static void DecodeSse2(Span scanline, Span previousScanline)
+ {
+ ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
+ ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
+
+ // Up(x) + Prior(x)
+ int rb = scanline.Length;
+ nint offset = 1;
+ const int bytesPerBatch = 16;
+ while (rb >= bytesPerBatch)
+ {
+ ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
+ Vector128 current = Unsafe.As>(ref scanRef);
+ Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset));
+
+ Vector128 sum = Sse2.Add(up, current);
+ Unsafe.As>(ref scanRef) = sum;
+
+ offset += bytesPerBatch;
+ rb -= bytesPerBatch;
+ }
+
+ // Handle left over.
+ for (nint i = offset; i < scanline.Length; i++)
+ {
+ ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset);
+ byte above = Unsafe.Add(ref prevBaseRef, offset);
+ scan = (byte)(scan + above);
+ offset++;
+ }
+ }
+#endif
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void DecodeScalar(Span scanline, Span previousScanline)
+ {
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
@@ -43,12 +128,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
}
///
- /// Encodes the scanline
+ /// Encodes a scanline with the up filter applied.
///
- /// The scanline to encode
+ /// The scanline to encode.
/// The previous scanline.
/// The filtered scanline result.
- /// The sum of the total variance of the filtered row
+ /// The sum of the total variance of the filtered row.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum)
{
diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs
index 04e70c51d..0b233848a 100644
--- a/src/ImageSharp/Formats/Png/PngDecoder.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoder.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@@ -17,18 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Png
public bool IgnoreMetadata { get; set; }
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
PngDecoderCore decoder = new(configuration, this);
- return decoder.Decode(configuration, stream);
+ return decoder.Decode(configuration, stream, cancellationToken);
}
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, true);
- IImageInfo info = decoder.Identify(configuration, stream);
+ IImageInfo info = decoder.Identify(configuration, stream, cancellationToken);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
@@ -40,118 +39,49 @@ namespace SixLabors.ImageSharp.Formats.Png
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
- ? this.Decode(configuration, stream)
- : this.Decode(configuration, stream);
+ ? this.Decode(configuration, stream, cancellationToken)
+ : this.Decode(configuration, stream, cancellationToken);
}
return !meta.HasTransparency
- ? this.Decode(configuration, stream)
- : this.Decode(configuration, stream);
+ ? this.Decode(configuration, stream, cancellationToken)
+ : this.Decode(configuration, stream, cancellationToken);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
- ? this.Decode(configuration, stream)
- : this.Decode(configuration, stream);
+ ? this.Decode(configuration, stream, cancellationToken)
+ : this.Decode(configuration, stream, cancellationToken);
}
return !meta.HasTransparency
- ? this.Decode(configuration, stream)
- : this.Decode(configuration, stream);
+ ? this.Decode(configuration, stream, cancellationToken)
+ : this.Decode(configuration, stream, cancellationToken);
case PngColorType.Palette:
- return this.Decode(configuration, stream);
+ return this.Decode(configuration, stream, cancellationToken);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
- ? this.Decode(configuration, stream)
- : this.Decode(configuration, stream);
+ ? this.Decode(configuration, stream, cancellationToken)
+ : this.Decode(configuration, stream, cancellationToken);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
- ? this.Decode(configuration, stream)
- : this.Decode(configuration, stream);
+ ? this.Decode(configuration, stream, cancellationToken)
+ : this.Decode(configuration, stream, cancellationToken);
default:
- return this.Decode(configuration, stream);
+ return this.Decode(configuration, stream, cancellationToken);
}
}
///
- public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- PngDecoderCore decoder = new(configuration, this);
- return decoder.DecodeAsync(configuration, stream, cancellationToken);
- }
-
- ///
- public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- {
- PngDecoderCore decoder = new(configuration, true);
- IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
- stream.Position = 0;
-
- PngMetadata meta = info.Metadata.GetPngMetadata();
- PngColorType color = meta.ColorType.GetValueOrDefault();
- PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
- switch (color)
- {
- case PngColorType.Grayscale:
- if (bits == PngBitDepth.Bit16)
- {
- return !meta.HasTransparency
- ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false)
- : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
- }
-
- return !meta.HasTransparency
- ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false)
- : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
-
- case PngColorType.Rgb:
- if (bits == PngBitDepth.Bit16)
- {
- return !meta.HasTransparency
- ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false)
- : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
- }
-
- return !meta.HasTransparency
- ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false)
- : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
-
- case PngColorType.Palette:
- return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
-
- case PngColorType.GrayscaleWithAlpha:
- return (bits == PngBitDepth.Bit16)
- ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false)
- : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
-
- case PngColorType.RgbWithAlpha:
- return (bits == PngBitDepth.Bit16)
- ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false)
- : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
-
- default:
- return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
- }
- }
-
- ///
- public IImageInfo Identify(Configuration configuration, Stream stream)
- {
- PngDecoderCore decoder = new(configuration, this);
- return decoder.Identify(configuration, stream);
- }
-
- ///
- public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
PngDecoderCore decoder = new(configuration, this);
- return decoder.IdentifyAsync(configuration, stream, cancellationToken);
+ return decoder.Identify(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index f5fc86ee4..f46b5058a 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -19,6 +19,7 @@ using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
@@ -205,6 +206,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true);
}
+ break;
+ case PngChunkType.EmbeddedColorProfile:
+ this.ReadColorProfileChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.End:
goto EOF;
@@ -227,10 +231,16 @@ namespace SixLabors.ImageSharp.Formats.Png
return image;
}
+ catch
+ {
+ image?.Dispose();
+ throw;
+ }
finally
{
this.scanline?.Dispose();
this.previousScanline?.Dispose();
+ this.nextChunk?.Data?.Dispose();
}
}
@@ -336,6 +346,14 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.End:
goto EOF;
+
+ default:
+ if (this.colorMetadataOnly)
+ {
+ this.SkipChunkDataAndCrc(chunk);
+ }
+
+ break;
}
}
finally
@@ -429,10 +447,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The metadata to read to.
/// The data containing physical data.
private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data)
+ {
+ if (data.Length < 4)
+ {
+ // Ignore invalid gamma chunks.
+ return;
+ }
- // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
// For example, a gamma of 1/2.2 would be stored as 45455.
- => pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
+ // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
+ pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
+ }
///
/// Initializes the image and various buffers needed for processing
@@ -457,6 +482,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
+ this.previousScanline?.Dispose();
+ this.scanline?.Dispose();
this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean);
}
@@ -1151,6 +1178,76 @@ namespace SixLabors.ImageSharp.Formats.Png
return true;
}
+ ///
+ /// Reads the color profile chunk. The data is stored similar to the zTXt chunk.
+ ///
+ /// The metadata.
+ /// The bytes containing the profile.
+ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan data)
+ {
+ int zeroIndex = data.IndexOf((byte)0);
+ if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength)
+ {
+ return;
+ }
+
+ byte compressionMethod = data[zeroIndex + 1];
+ if (compressionMethod != 0)
+ {
+ // Only compression method 0 is supported (zlib datastream with deflate compression).
+ return;
+ }
+
+ ReadOnlySpan keywordBytes = data.Slice(0, zeroIndex);
+ if (!this.TryReadTextKeyword(keywordBytes, out string name))
+ {
+ return;
+ }
+
+ ReadOnlySpan compressedData = data.Slice(zeroIndex + 2);
+
+ if (this.TryUncompressZlibData(compressedData, out byte[] iccpProfileBytes))
+ {
+ metadata.IccProfile = new IccProfile(iccpProfileBytes);
+ }
+ }
+
+ ///
+ /// Tries to un-compress zlib compressed data.
+ ///
+ /// The compressed data.
+ /// The uncompressed bytes array.
+ /// True, if de-compressing was successful.
+ private unsafe bool TryUncompressZlibData(ReadOnlySpan compressedData, out byte[] uncompressedBytesArray)
+ {
+ fixed (byte* compressedDataBase = compressedData)
+ {
+ using (IMemoryOwner destBuffer = this.memoryAllocator.Allocate(this.Configuration.StreamProcessingBufferSize))
+ using (var memoryStreamOutput = new MemoryStream(compressedData.Length))
+ using (var memoryStreamInput = new UnmanagedMemoryStream(compressedDataBase, compressedData.Length))
+ using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStreamInput))
+ using (var inflateStream = new ZlibInflateStream(bufferedStream))
+ {
+ Span destUncompressedData = destBuffer.GetSpan();
+ if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
+ {
+ uncompressedBytesArray = Array.Empty();
+ return false;
+ }
+
+ int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
+ while (bytesRead != 0)
+ {
+ memoryStreamOutput.Write(destUncompressedData.Slice(0, bytesRead));
+ bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
+ }
+
+ uncompressedBytesArray = memoryStreamOutput.ToArray();
+ return true;
+ }
+ }
+ }
+
///
/// Compares two ReadOnlySpan<char>s in a case-insensitive method.
/// This is only needed because older frameworks are missing the extension method.
@@ -1283,7 +1380,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
else if (this.IsXmpTextData(keywordBytes))
{
- XmpProfile xmpProfile = new XmpProfile(data.Slice(dataStartIdx).ToArray());
+ var xmpProfile = new XmpProfile(data.Slice(dataStartIdx).ToArray());
metadata.XmpProfile = xmpProfile;
}
else
@@ -1302,29 +1399,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The .
private bool TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, out string value)
{
- using (var memoryStream = new MemoryStream(compressedData.ToArray()))
- using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStream))
- using (var inflateStream = new ZlibInflateStream(bufferedStream))
+ if (this.TryUncompressZlibData(compressedData, out byte[] uncompressedData))
{
- if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
- {
- value = null;
- return false;
- }
-
- var uncompressedBytes = new List();
-
- // Note: this uses a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
- int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
- while (bytesRead != 0)
- {
- uncompressedBytes.AddRange(this.buffer.AsSpan(0, bytesRead).ToArray());
- bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
- }
-
- value = encoding.GetString(uncompressedBytes.ToArray());
+ value = encoding.GetString(uncompressedData);
return true;
}
+
+ value = null;
+ return false;
}
///
@@ -1344,6 +1426,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
if (chunk.Type == PngChunkType.Data)
{
+ chunk.Data?.Dispose();
return chunk.Length;
}
@@ -1438,6 +1521,9 @@ namespace SixLabors.ImageSharp.Formats.Png
if (validCrc != inputCrc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
+
+ // ensure when throwing we dispose the data back to the memory allocator
+ chunk.Data?.Dispose();
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
}
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index c443c0fcf..ad16c8037 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -87,6 +87,11 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private IMemoryOwner currentScanline;
+ ///
+ /// The color profile name.
+ ///
+ private const string ColorProfileName = "ICC Profile";
+
///
/// Initializes a new instance of the class.
///
@@ -134,6 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WriteHeaderChunk(stream);
this.WriteGammaChunk(stream);
+ this.WriteColorProfileChunk(stream, metadata);
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
this.WritePhysicalChunk(stream, metadata);
@@ -656,7 +662,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
///
- /// Writes an iTXT chunk, containing the XMP metdata to the stream, if such profile is present in the metadata.
+ /// Writes an iTXT chunk, containing the XMP metadata to the stream, if such profile is present in the metadata.
///
/// The containing image data.
/// The image metadata.
@@ -673,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return;
}
- var xmpData = meta.XmpProfile.Data;
+ byte[] xmpData = meta.XmpProfile.Data;
if (xmpData.Length == 0)
{
@@ -687,19 +693,49 @@ namespace SixLabors.ImageSharp.Formats.Png
PngConstants.XmpKeyword.CopyTo(payload);
int bytesWritten = PngConstants.XmpKeyword.Length;
- // Write the iTxt header (all zeros in this case)
- payload[bytesWritten++] = 0;
- payload[bytesWritten++] = 0;
- payload[bytesWritten++] = 0;
- payload[bytesWritten++] = 0;
- payload[bytesWritten++] = 0;
+ // Write the iTxt header (all zeros in this case).
+ Span iTxtHeader = payload.Slice(bytesWritten);
+ iTxtHeader[4] = 0;
+ iTxtHeader[3] = 0;
+ iTxtHeader[2] = 0;
+ iTxtHeader[1] = 0;
+ iTxtHeader[0] = 0;
+ bytesWritten += 5;
- // And the XMP data itself
+ // And the XMP data itself.
xmpData.CopyTo(payload.Slice(bytesWritten));
this.WriteChunk(stream, PngChunkType.InternationalText, payload);
}
}
+ ///
+ /// Writes the color profile chunk.
+ ///
+ /// The stream to write to.
+ /// The image meta data.
+ private void WriteColorProfileChunk(Stream stream, ImageMetadata metaData)
+ {
+ if (metaData.IccProfile is null)
+ {
+ return;
+ }
+
+ byte[] iccProfileBytes = metaData.IccProfile.ToByteArray();
+
+ byte[] compressedData = this.GetZlibCompressedBytes(iccProfileBytes);
+ int payloadLength = ColorProfileName.Length + compressedData.Length + 2;
+ using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength))
+ {
+ Span outputBytes = owner.GetSpan();
+ PngConstants.Encoding.GetBytes(ColorProfileName).CopyTo(outputBytes);
+ int bytesWritten = ColorProfileName.Length;
+ outputBytes[bytesWritten++] = 0; // Null separator.
+ outputBytes[bytesWritten++] = 0; // Compression.
+ compressedData.CopyTo(outputBytes.Slice(bytesWritten));
+ this.WriteChunk(stream, PngChunkType.EmbeddedColorProfile, outputBytes);
+ }
+ }
+
///
/// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk,
/// depending whether the text contains any latin characters or should be compressed.
@@ -727,13 +763,12 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
- if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) ||
- !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
+ if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
{
// Write iTXt chunk.
byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword);
byte[] textBytes = textData.Value.Length > this.options.TextCompressionThreshold
- ? this.GetCompressedTextBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value))
+ ? this.GetZlibCompressedBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value))
: PngConstants.TranslatedEncoding.GetBytes(textData.Value);
byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword);
@@ -772,18 +807,17 @@ namespace SixLabors.ImageSharp.Formats.Png
if (textData.Value.Length > this.options.TextCompressionThreshold)
{
// Write zTXt chunk.
- byte[] compressedData =
- this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value));
+ byte[] compressedData = this.GetZlibCompressedBytes(PngConstants.Encoding.GetBytes(textData.Value));
int payloadLength = textData.Keyword.Length + compressedData.Length + 2;
using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength))
{
Span outputBytes = owner.GetSpan();
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
int bytesWritten = textData.Keyword.Length;
- outputBytes[bytesWritten++] = 0;
- outputBytes[bytesWritten++] = 0;
+ outputBytes[bytesWritten++] = 0; // Null separator.
+ outputBytes[bytesWritten++] = 0; // Compression.
compressedData.CopyTo(outputBytes.Slice(bytesWritten));
- this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes.ToArray());
+ this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes);
}
}
else
@@ -796,9 +830,8 @@ namespace SixLabors.ImageSharp.Formats.Png
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
int bytesWritten = textData.Keyword.Length;
outputBytes[bytesWritten++] = 0;
- PngConstants.Encoding.GetBytes(textData.Value)
- .CopyTo(outputBytes.Slice(bytesWritten));
- this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray());
+ PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes.Slice(bytesWritten));
+ this.WriteChunk(stream, PngChunkType.Text, outputBytes);
}
}
}
@@ -808,15 +841,15 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Compresses a given text using Zlib compression.
///
- /// The text bytes to compress.
- /// The compressed text byte array.
- private byte[] GetCompressedTextBytes(byte[] textBytes)
+ /// The bytes to compress.
+ /// The compressed byte array.
+ private byte[] GetZlibCompressedBytes(byte[] dataBytes)
{
using (var memoryStream = new MemoryStream())
{
using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
- deflateStream.Write(textBytes);
+ deflateStream.Write(dataBytes);
}
return memoryStream.ToArray();
diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
index 58fa5aca8..26bc566d6 100644
--- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
+++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
@@ -240,6 +240,11 @@ namespace SixLabors.ImageSharp.Formats.Png
byte[] paletteAlpha)
where TPixel : unmanaged, IPixel
{
+ if (palette.IsEmpty)
+ {
+ PngThrowHelper.ThrowMissingPalette();
+ }
+
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
index 8700438bd..ae7d16ec7 100644
--- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -21,6 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Png
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk");
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowMissingPalette() => throw new InvalidImageContentException("PNG Image does not contain a palette chunk");
+
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data.");
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
index e06a0ee88..bb0a0d548 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
@@ -3,9 +3,6 @@
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
@@ -16,48 +13,25 @@ namespace SixLabors.ImageSharp.Formats.Tga
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
{
///
- public Image Decode(Configuration configuration, Stream stream)
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
- return decoder.Decode(configuration, stream);
+ return decoder.Decode(configuration, stream, cancellationToken);
}
///
- public Image Decode(Configuration configuration, Stream stream)
- => this.Decode(configuration, stream);
+ public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ => this.Decode(configuration, stream, cancellationToken);
///
- public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- Guard.NotNull(stream, nameof(stream));
-
- var decoder = new TgaDecoderCore(configuration, this);
- return decoder.DecodeAsync(configuration, stream, cancellationToken);
- }
-
- ///
- public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
- => await this.DecodeAsync(configuration, stream, cancellationToken)
- .ConfigureAwait(false);
-
- ///
- public IImageInfo Identify(Configuration configuration, Stream stream)
- {
- Guard.NotNull(stream, nameof(stream));
-
- return new TgaDecoderCore(configuration, this).Identify(configuration, stream);
- }
-
- ///
- public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
+ public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
- return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
+ return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
index 30da537eb..d038e9c8b 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
-using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
@@ -13,222 +11,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
///
/// Bitwriter for writing compressed CCITT T4 1D data.
///
- internal sealed class T4BitCompressor : TiffBaseCompressor
+ internal sealed class T4BitCompressor : TiffCcittCompressor
{
- private const uint WhiteZeroRunTermCode = 0x35;
-
- private 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 WhiteLen4TermCodes = new Dictionary()
- {
- { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
- };
-
- private static readonly Dictionary WhiteLen5TermCodes = new Dictionary()
- {
- { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
- };
-
- private static readonly Dictionary WhiteLen6TermCodes = new Dictionary()
- {
- { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B }
- };
-
- private static readonly Dictionary WhiteLen7TermCodes = new Dictionary